asciidoctor-pdf 1.5.3 → 1.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +70 -1
  3. data/{LICENSE.adoc → LICENSE} +3 -4
  4. data/NOTICE.adoc +4 -6
  5. data/README.adoc +139 -46
  6. data/asciidoctor-pdf.gemspec +11 -14
  7. data/bin/asciidoctor-pdf-optimize +1 -1
  8. data/docs/theming-guide.adoc +34 -28
  9. data/lib/asciidoctor/pdf/converter.rb +87 -77
  10. data/lib/asciidoctor/pdf/ext/asciidoctor/section.rb +3 -2
  11. data/lib/asciidoctor/pdf/ext/core/file.rb +4 -5
  12. data/lib/asciidoctor/pdf/ext/core.rb +0 -1
  13. data/lib/asciidoctor/pdf/ext/pdf-core/pdf_object.rb +1 -1
  14. data/lib/asciidoctor/pdf/ext/pdf-core.rb +17 -0
  15. data/lib/asciidoctor/pdf/ext/prawn/coderay_encoder.rb +4 -13
  16. data/lib/asciidoctor/pdf/ext/prawn/extensions.rb +28 -2
  17. data/lib/asciidoctor/pdf/ext/prawn/font_metric_cache.rb +1 -1
  18. data/lib/asciidoctor/pdf/ext/prawn/formatted_text/arranger.rb +15 -0
  19. data/lib/asciidoctor/pdf/ext/prawn-svg.rb +0 -1
  20. data/lib/asciidoctor/pdf/ext/prawn.rb +1 -0
  21. data/lib/asciidoctor/pdf/ext/pygments.rb +11 -9
  22. data/lib/asciidoctor/pdf/ext.rb +0 -1
  23. data/lib/asciidoctor/pdf/formatted_text/fragment_position_renderer.rb +5 -1
  24. data/lib/asciidoctor/pdf/formatted_text/inline_image_arranger.rb +8 -1
  25. data/lib/asciidoctor/pdf/formatted_text/transform.rb +6 -4
  26. data/lib/asciidoctor/pdf/index_catalog.rb +1 -1
  27. data/lib/asciidoctor/pdf/optimizer.rb +40 -12
  28. data/lib/asciidoctor/pdf/pdfmark.rb +1 -2
  29. data/lib/asciidoctor/pdf/text_transformer.rb +10 -73
  30. data/lib/asciidoctor/pdf/theme_loader.rb +1 -1
  31. data/lib/asciidoctor/pdf/version.rb +1 -1
  32. data/lib/asciidoctor/pdf.rb +3 -0
  33. metadata +21 -63
  34. data/lib/asciidoctor/pdf/ext/core/numeric.rb +0 -26
  35. data/lib/asciidoctor/pdf/ext/prawn-svg/interface.rb +0 -14
  36. data/lib/asciidoctor/pdf/ext/prawn-templates.rb +0 -7
@@ -1,4 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ module PDF::Core
4
+ class << self
5
+ alias _initial_real real
6
+
7
+ # NOTE Makes v1.6.x work with the modified precision settings (0.5f) in Prawn 2.4 while preserving existing behavior
8
+ def real num, precision = 4
9
+ ("%.#{precision}f" % num).sub(/((?<!\.)0)+\z/, '')
10
+ end
11
+
12
+ alias _initial_real_params real_params
13
+ def real_params array
14
+ return array.map {|e| real e, 5 }.join(' ') if (caller_locations 1, 1)[0].base_label == 'transformation_matrix'
15
+ _initial_real_params array
16
+ end
17
+ end
18
+ end
19
+
3
20
  require_relative 'pdf-core/pdf_object'
4
21
  require_relative 'pdf-core/page'
@@ -5,21 +5,12 @@
5
5
  # This file was copied from Prawn (manual/syntax_highlight.rb) and
6
6
  # modified for use with Asciidoctor PDF.
7
7
  #
8
- # Prawn is free software: you can redistribute it and/or modify
9
- # it under the terms of the GNU General Public License as published by
10
- # the Free Software Foundation, either version 3 of the License, or
11
- # (at your option) any later version.
12
- #
13
- # Prawn is distributed in the hope that it will be useful,
14
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
- # GNU General Public License for more details.
17
- #
18
- # You should have received a copy of the GNU General Public License
19
- # along with Prawn. If not, see <http://www.gnu.org/licenses/>.
8
+ # Since the file originates from the Prawn project, it shares the Prawn
9
+ # license. Thus, the file may be used under Matz's original licensing terms for
10
+ # Ruby, the GPLv2 license, or the GPLv3 license.
20
11
  #
21
12
  # Copyright (C) Felipe Doria
22
- # Copyright (C) 2014 OpenDevise Inc. and the Asciidoctor Project
13
+ # Copyright (C) 2014-present OpenDevise Inc. and the Asciidoctor Project
23
14
  #
24
15
  ######################################################################
25
16
 
@@ -76,6 +76,27 @@ module Asciidoctor
76
76
  reference_bounds.height
77
77
  end
78
78
 
79
+ # workaround for https://github.com/prawnpdf/prawn/issues/1121
80
+ def generate_margin_box
81
+ page_w, page_h = (page = state.page).dimensions.slice 2, 2
82
+ page_m = page.margins
83
+ prev_margin_box, @margin_box = @margin_box, (::Prawn::Document::BoundingBox.new self, nil, [page_m[:left], page_h - page_m[:top]], width: page_w - page_m[:left] - page_m[:right], height: page_h - page_m[:top] - page_m[:bottom])
84
+
85
+ # update bounding box if not flowing from the previous page
86
+ unless @bounding_box&.parent
87
+ prev_margin_box = @bounding_box
88
+ @bounding_box = @margin_box
89
+ end
90
+
91
+ # maintains indentation settings across page breaks
92
+ if prev_margin_box
93
+ @margin_box.add_left_padding prev_margin_box.total_left_padding
94
+ @margin_box.add_right_padding prev_margin_box.total_right_padding
95
+ end
96
+
97
+ nil
98
+ end
99
+
79
100
  # Set the margins for the current page.
80
101
  #
81
102
  def set_page_margin margin
@@ -190,7 +211,7 @@ module Asciidoctor
190
211
  # }
191
212
  #
192
213
  def register_font data
193
- font_families.update data.each_with_object({}) {|(key, val), accum| accum[key.to_s] = val }
214
+ font_families.update data.transform_keys(&:to_s)
194
215
  end
195
216
 
196
217
  # Enhances the built-in font method to allow the font
@@ -553,7 +574,7 @@ module Asciidoctor
553
574
 
554
575
  # TODO: memoize the result
555
576
  def inflate_padding padding
556
- padding = [*(padding || 0)].slice 0, 4
577
+ padding = (Array padding || 0).slice 0, 4
557
578
  case padding.size
558
579
  when 1
559
580
  [padding[0], padding[0], padding[0], padding[0]]
@@ -882,6 +903,11 @@ module Asciidoctor
882
903
  [(whole_pages * full_page_height + partial_page_height), whole_pages, partial_page_height]
883
904
  end
884
905
 
906
+ def with_dry_run &block
907
+ total_height, = dry_run(&block)
908
+ instance_exec total_height, &block
909
+ end
910
+
885
911
  # Attempt to keep the objects generated in the block on the same page
886
912
  #
887
913
  # TODO: short-circuit nested usage
@@ -6,4 +6,4 @@ class Prawn::FontMetricCache::CacheEntry
6
6
  font = font.hash
7
7
  super
8
8
  end
9
- end
9
+ end if Prawn::FontMetricCache::CacheEntry.members == [:font, :options, :string]
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ Prawn::Text::Formatted::Arranger.prepend (Module.new do
4
+ def initialize *_args
5
+ super
6
+ @dummy_text = ?\u0000
7
+ end
8
+
9
+ def next_string
10
+ if (string = super) == @dummy_text
11
+ def string.lstrip!; end
12
+ end
13
+ string
14
+ end
15
+ end)
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'prawn-svg' unless defined? Prawn::SVG::Interface
4
- require_relative 'prawn-svg/interface'
5
4
  # NOTE disable system fonts since they're non-portable
6
5
  Prawn::SVG::Interface.font_path.clear
@@ -4,6 +4,7 @@
4
4
  require_relative 'prawn/font_metric_cache'
5
5
  require_relative 'prawn/font/afm'
6
6
  require_relative 'prawn/images'
7
+ require_relative 'prawn/formatted_text/arranger'
7
8
  require_relative 'prawn/formatted_text/box'
8
9
  require_relative 'prawn/formatted_text/fragment'
9
10
  require_relative 'prawn/extensions'
@@ -1,23 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'pygments.rb'
4
-
5
3
  module Pygments
6
4
  module Ext
7
5
  module BlockStyles
8
6
  BlockSelectorRx = /^\.highlight *\{([^}]+?)\}/
9
7
  HighlightBackgroundColorRx = /^\.highlight +\.hll +{ *background(?:-color)?: *#([a-fA-F0-9]{6})/
10
- HexColorRx = /^#[a-fA-F0-9]{6}$/
8
+ ColorPropertiesRx = /(?:^|;) *(background(?:-color)?|color): *#?([a-fA-F0-9]{6}) *(?=$|;)/
11
9
 
12
10
  @cache = ::Hash.new do |cache, key|
13
11
  styles = {}
14
12
  if BlockSelectorRx =~ (css = ::Pygments.css '.highlight', style: key)
15
- ($1.strip.split ';').each do |style|
16
- pname, pval = (style.split ':', 2).map(&:strip)
17
- if pname == 'background' || pname == 'background-color'
18
- styles[:background_color] = pval.slice 1, pval.length if HexColorRx.match? pval
19
- elsif pname == 'color'
20
- styles[:font_color] = pval.slice 1, pval.length if HexColorRx.match? pval
13
+ $1.scan ColorPropertiesRx do |pname, pval|
14
+ case pname
15
+ when 'background', 'background-color'
16
+ styles[:background_color] = pval
17
+ when 'color'
18
+ styles[:font_color] = pval
21
19
  end
22
20
  end
23
21
  end
@@ -26,6 +24,10 @@ module Pygments
26
24
  styles
27
25
  end
28
26
 
27
+ def self.available? style
28
+ (@available ||= ::Pygments.styles.to_set).include? style
29
+ end
30
+
29
31
  def self.for style
30
32
  @cache[style]
31
33
  end
@@ -6,4 +6,3 @@ require_relative 'ext/pdf-core'
6
6
  require_relative 'ext/prawn'
7
7
  require_relative 'ext/prawn-svg'
8
8
  require_relative 'ext/prawn-table'
9
- require_relative 'ext/prawn-templates'
@@ -2,7 +2,11 @@
2
2
 
3
3
  module Asciidoctor::PDF::FormattedText
4
4
  class FragmentPositionRenderer
5
- attr_reader :top, :right, :bottom, :left, :page_number
5
+ attr_reader :top
6
+ attr_reader :right
7
+ attr_reader :bottom
8
+ attr_reader :left
9
+ attr_reader :page_number
6
10
 
7
11
  def render_behind fragment
8
12
  @top = fragment.top
@@ -39,8 +39,14 @@ module Asciidoctor::PDF::FormattedText
39
39
  scratch = doc.scratch?
40
40
  available_w = doc.bounds.width
41
41
  available_h = doc.page.empty? ? doc.cursor : doc.bounds.height
42
+ last_fragment = {}
42
43
  raw_image_fragments.each do |fragment|
43
- drop = scratch
44
+ if fragment[:object_id] == last_fragment[:object_id]
45
+ fragments.delete fragment
46
+ next
47
+ else
48
+ drop = scratch
49
+ end
44
50
  begin
45
51
  image_path = fragment[:image_path]
46
52
 
@@ -125,6 +131,7 @@ module Asciidoctor::PDF::FormattedText
125
131
  # NOTE retain key to indicate we've visited fragment already
126
132
  fragment[:image_obj] = nil
127
133
  end
134
+ last_fragment = fragment
128
135
  end
129
136
  end
130
137
  end
@@ -172,9 +172,10 @@ module Asciidoctor
172
172
  fragment = {
173
173
  image_path: attributes[:src],
174
174
  image_format: attributes[:format],
175
- # a zero-width space in the text will cause the image to be duplicated
176
- text: (attributes[:alt].delete ZeroWidthSpace),
175
+ # NOTE: add enclosing square brackets here to avoid errors in parsing
176
+ text: %([#{attributes[:alt].delete ZeroWidthSpace}]),
177
177
  callback: [InlineImageRenderer],
178
+ object_id: node.object_id, # used to deduplicate if fragment gets split up
178
179
  }
179
180
  if inherited && (link = inherited[:link])
180
181
  fragment[:link] = link
@@ -379,9 +380,10 @@ module Asciidoctor
379
380
 
380
381
  def update_fragment fragment, props
381
382
  fragment.update props do |k, oval, nval|
382
- if k == :styles
383
+ case k
384
+ when :styles
383
385
  nval ? (oval.merge nval) : oval.clear
384
- elsif k == :callback
386
+ when :callback
385
387
  oval | nval
386
388
  else
387
389
  nval
@@ -32,7 +32,7 @@ module Asciidoctor
32
32
 
33
33
  def store_primary_term name, dest = nil
34
34
  store_dest dest if dest
35
- (init_category uppercase_mb name.chr).store_term name, dest
35
+ (init_category name.chr.upcase).store_term name, dest
36
36
  end
37
37
 
38
38
  def store_secondary_term primary_name, secondary_name, dest = nil
@@ -2,11 +2,35 @@
2
2
 
3
3
  require 'pathname'
4
4
  require 'rghost'
5
+ require 'rghost/gs_alone'
5
6
  require 'tmpdir'
6
7
 
8
+ RGhost::GSAlone.prepend (Module.new do
9
+ WindowsRx = /win|ming/
10
+
11
+ def initialize params, debug
12
+ (@params = params.dup).push(*(@params.pop.split File::PATH_SEPARATOR))
13
+ @debug = debug
14
+ end
15
+
16
+ def run
17
+ RGhost::Config.config_platform unless File.exist? RGhost::Config::GS[:path].to_s
18
+ (cmd = @params.slice 1, @params.length).unshift RGhost::Config::GS[:path].to_s
19
+ #puts cmd if @debug
20
+ system(*cmd)
21
+ end
22
+ end)
23
+
24
+ RGhost::Engine.prepend (Module.new do
25
+ def shellescape str
26
+ str
27
+ end
28
+ end)
29
+
7
30
  module Asciidoctor
8
31
  module PDF
9
32
  class Optimizer
33
+ # see https://www.ghostscript.com/doc/current/VectorDevices.htm#PSPDF_IN for details
10
34
  (QUALITY_NAMES = {
11
35
  'default' => :default,
12
36
  'screen' => :screen,
@@ -15,27 +39,31 @@ module Asciidoctor
15
39
  'prepress' => :prepress,
16
40
  }).default = :default
17
41
 
42
+ attr_reader :quality
43
+ attr_reader :compatibility_level
44
+
18
45
  def initialize quality = 'default', compatibility_level = '1.4'
19
46
  @quality = QUALITY_NAMES[quality]
20
47
  @compatibility_level = compatibility_level
48
+ if (gs_path = ::ENV['GS'])
49
+ ::RGhost::Config::GS[:path] = gs_path
50
+ end
21
51
  end
22
52
 
23
- def generate_file target
53
+ def optimize_file target
24
54
  ::Dir::Tmpname.create ['asciidoctor-pdf-', '.pdf'] do |tmpfile|
25
- filename = Pathname.new target
26
- filename_o = Pathname.new tmpfile
27
- pdfmark = filename.sub_ext '.pdfmark'
28
- inputs = pdfmark.file? ? [target, pdfmark.to_s] : target
55
+ filename_o = ::Pathname.new target
56
+ filename_tmp = ::Pathname.new tmpfile
57
+ if (pdfmark = filename_o.sub_ext '.pdfmark').file?
58
+ inputs = [target, pdfmark.to_s].join ::File::PATH_SEPARATOR
59
+ else
60
+ inputs = target
61
+ end
29
62
  (::RGhost::Convert.new inputs).to :pdf,
30
- filename: filename_o.to_s,
63
+ filename: filename_tmp.to_s,
31
64
  quality: @quality,
32
65
  d: { Printed: false, CannotEmbedFontPolicy: '/Warning', CompatibilityLevel: @compatibility_level }
33
- begin
34
- filename_o.rename target
35
- rescue ::Errno::EXDEV
36
- filename.binwrite filename_o.binread
37
- filename_o.unlink
38
- end
66
+ filename_o.binwrite filename_tmp.binread
39
67
  end
40
68
  nil
41
69
  end
@@ -18,7 +18,7 @@ module Asciidoctor
18
18
  creation_date = (::Time.parse doc.attr 'localdatetime') rescue (now || ::Time.now)
19
19
  end
20
20
  # FIXME: use sanitize: :plain_text once available
21
- content = <<~EOS
21
+ <<~EOS
22
22
  [ /Title #{(sanitize doc.doctitle use_fallback: true).to_pdf_object}
23
23
  /Author #{(doc.attr 'authors').to_pdf_object}
24
24
  /Subject #{(doc.attr 'subject').to_pdf_object}
@@ -29,7 +29,6 @@ module Asciidoctor
29
29
  /Producer #{(doc.attr 'publisher').to_pdf_object}
30
30
  /DOCINFO pdfmark
31
31
  EOS
32
- content
33
32
  end
34
33
 
35
34
  def generate_file pdf_file
@@ -1,36 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- unless RUBY_VERSION >= '2.4'
4
- begin
5
- require 'unicode' unless defined? Unicode::VERSION
6
- rescue LoadError
7
- begin
8
- require 'active_support/multibyte' unless defined? ActiveSupport::Multibyte
9
- rescue LoadError; end
10
- end
11
- end
12
-
13
3
  module Asciidoctor
14
4
  module PDF
15
5
  module TextTransformer
16
6
  XMLMarkupRx = /&#?[a-z\d]+;|</
17
7
  PCDATAFilterRx = /(&#?[a-z\d]+;|<[^>]+>)|([^&<]+)/
18
8
  TagFilterRx = /(<[^>]+>)|([^<]+)/
19
- WordRx = /\S+/
9
+ ContiguousCharsRx = /\p{Graph}+/
10
+ WordRx = /\p{Word}+/
20
11
  Hyphen = '-'
21
12
  SoftHyphen = ?\u00ad
22
- HyphenatedHyphen = '-' + SoftHyphen
23
13
 
24
14
  def capitalize_words_pcdata string
25
15
  if XMLMarkupRx.match? string
26
- string.gsub(PCDATAFilterRx) { $2 ? (capitalize_words_mb $2) : $1 }
16
+ string.gsub(PCDATAFilterRx) { $2 ? (capitalize_words $2) : $1 }
27
17
  else
28
- capitalize_words_mb string
18
+ capitalize_words string
29
19
  end
30
20
  end
31
21
 
32
- def capitalize_words_mb string
33
- string.gsub(WordRx) { capitalize_mb $& }
22
+ def capitalize_words string
23
+ string.gsub(ContiguousCharsRx) { $&.capitalize }
34
24
  end
35
25
 
36
26
  def hyphenate_words_pcdata string, hyphenator
@@ -42,74 +32,21 @@ module Asciidoctor
42
32
  end
43
33
 
44
34
  def hyphenate_words string, hyphenator
45
- string.gsub(WordRx) { (hyphenator.visualize $&, SoftHyphen).gsub HyphenatedHyphen, Hyphen }
35
+ string.gsub(WordRx) { hyphenator.visualize $&, SoftHyphen }
46
36
  end
47
37
 
48
38
  def lowercase_pcdata string
49
39
  if string.include? '<'
50
- string.gsub(TagFilterRx) { $2 ? (lowercase_mb $2) : $1 }
40
+ string.gsub(TagFilterRx) { $2 ? $2.downcase : $1 }
51
41
  else
52
- lowercase_mb string
42
+ string.downcase
53
43
  end
54
44
  end
55
45
 
56
46
  def uppercase_pcdata string
57
47
  if XMLMarkupRx.match? string
58
- string.gsub(PCDATAFilterRx) { $2 ? (uppercase_mb $2) : $1 }
48
+ string.gsub(PCDATAFilterRx) { $2 ? $2.upcase : $1 }
59
49
  else
60
- uppercase_mb string
61
- end
62
- end
63
-
64
- if RUBY_VERSION >= '2.4'
65
- def capitalize_mb string
66
- string.capitalize
67
- end
68
-
69
- def lowercase_mb string
70
- string.downcase
71
- end
72
-
73
- def uppercase_mb string
74
- string.upcase
75
- end
76
- # NOTE Unicode library is 4x as fast as ActiveSupport::MultiByte::Chars
77
- elsif defined? ::Unicode
78
- def capitalize_mb string
79
- string.ascii_only? ? string.capitalize : (::Unicode.capitalize string)
80
- end
81
-
82
- def lowercase_mb string
83
- string.ascii_only? ? string.downcase : (::Unicode.downcase string)
84
- end
85
-
86
- def uppercase_mb string
87
- string.ascii_only? ? string.upcase : (::Unicode.upcase string)
88
- end
89
- elsif defined? ::ActiveSupport::Multibyte
90
- MultibyteChars = ::ActiveSupport::Multibyte::Chars
91
-
92
- def capitalize_mb string
93
- string.ascii_only? ? string.capitalize : (MultibyteChars.new string).capitalize.to_s
94
- end
95
-
96
- def lowercase_mb string
97
- string.ascii_only? ? string.downcase : (MultibyteChars.new string).downcase.to_s
98
- end
99
-
100
- def uppercase_mb string
101
- string.ascii_only? ? string.upcase : (MultibyteChars.new string).upcase.to_s
102
- end
103
- else
104
- def capitalize_mb string
105
- string.capitalize
106
- end
107
-
108
- def lowercase_mb string
109
- string.downcase
110
- end
111
-
112
- def uppercase_mb string
113
50
  string.upcase
114
51
  end
115
52
  end
@@ -94,7 +94,7 @@ module Asciidoctor
94
94
  yaml_data = ::SafeYAML.load data, filename
95
95
  if ::Hash === yaml_data && (yaml_data.key? 'extends')
96
96
  if (extends = yaml_data.delete 'extends')
97
- [*extends].each do |extend_path|
97
+ (Array extends).each do |extend_path|
98
98
  if extend_path == 'base'
99
99
  theme_data = theme_data ? (::OpenStruct.new theme_data.to_h.merge load_base_theme.to_h) : load_base_theme
100
100
  next
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Asciidoctor
4
4
  module PDF
5
- VERSION = '1.5.3'
5
+ VERSION = '1.6.2'
6
6
  end
7
7
  Pdf = PDF unless const_defined? :Pdf, false
8
8
  end
@@ -3,6 +3,9 @@
3
3
  require_relative 'pdf/version'
4
4
  require 'asciidoctor' unless defined? Asciidoctor.load
5
5
  require 'prawn'
6
+ Prawn.send :remove_const, :FLOAT_PRECISION
7
+ Prawn::FLOAT_PRECISION = 1e-3
8
+ require 'prawn/templates'
6
9
  begin
7
10
  require 'prawn/gmagick'
8
11
  rescue LoadError