asciidoctor-pdf 1.6.1 → 2.0.0.alpha.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/CHANGELOG.adoc +273 -31
  4. data/NOTICE.adoc +16 -4
  5. data/README.adoc +208 -68
  6. data/asciidoctor-pdf.gemspec +3 -7
  7. data/data/fonts/ABOUT-mplus1mn-subset +1 -1
  8. data/data/fonts/ABOUT-mplus1p-subset +2 -2
  9. data/data/fonts/ABOUT-notosans-subset +26 -0
  10. data/data/fonts/ABOUT-notoserif-subset +1 -1
  11. data/data/fonts/{LICENSE-notoserif → LICENSE-noto} +0 -0
  12. data/data/fonts/mplus1mn-bold-subset.ttf +0 -0
  13. data/data/fonts/mplus1mn-bold_italic-subset.ttf +0 -0
  14. data/data/fonts/mplus1mn-italic-subset.ttf +0 -0
  15. data/data/fonts/mplus1mn-regular-subset.ttf +0 -0
  16. data/data/fonts/mplus1p-regular-fallback.ttf +0 -0
  17. data/data/fonts/notoemoji-subset.ttf +0 -0
  18. data/data/fonts/notosans-bold-subset.ttf +0 -0
  19. data/data/fonts/notosans-bold_italic-subset.ttf +0 -0
  20. data/data/fonts/notosans-italic-subset.ttf +0 -0
  21. data/data/fonts/notosans-regular-subset.ttf +0 -0
  22. data/data/fonts/notoserif-bold-subset.ttf +0 -0
  23. data/data/fonts/notoserif-bold_italic-subset.ttf +0 -0
  24. data/data/fonts/notoserif-italic-subset.ttf +0 -0
  25. data/data/fonts/notoserif-regular-subset.ttf +0 -0
  26. data/data/themes/base-theme.yml +21 -24
  27. data/data/themes/default-for-print-theme.yml +24 -0
  28. data/data/themes/default-for-print-with-fallback-font-theme.yml +3 -0
  29. data/data/themes/default-theme.yml +55 -59
  30. data/data/themes/default-with-fallback-font-theme.yml +2 -2
  31. data/data/themes/sans-with-fallback-font-theme.yml +10 -0
  32. data/docs/theming-guide.adoc +977 -352
  33. data/lib/asciidoctor/pdf/converter.rb +1853 -1566
  34. data/lib/asciidoctor/pdf/ext/asciidoctor/document.rb +22 -1
  35. data/lib/asciidoctor/pdf/ext/asciidoctor/image.rb +9 -15
  36. data/lib/asciidoctor/pdf/ext/asciidoctor/list.rb +6 -13
  37. data/lib/asciidoctor/pdf/ext/asciidoctor/section.rb +3 -16
  38. data/lib/asciidoctor/pdf/ext/asciidoctor.rb +1 -5
  39. data/lib/asciidoctor/pdf/ext/core/file.rb +1 -1
  40. data/lib/asciidoctor/pdf/ext/core/quantifiable_stdout.rb +1 -4
  41. data/lib/asciidoctor/pdf/ext/core/string.rb +2 -2
  42. data/lib/asciidoctor/pdf/ext/core.rb +1 -4
  43. data/lib/asciidoctor/pdf/ext/pdf-core/page.rb +8 -33
  44. data/lib/asciidoctor/pdf/ext/pdf-core.rb +0 -16
  45. data/lib/asciidoctor/pdf/ext/prawn/coderay_encoder.rb +5 -7
  46. data/lib/asciidoctor/pdf/ext/prawn/extensions.rb +489 -331
  47. data/lib/asciidoctor/pdf/ext/prawn/font/afm.rb +0 -4
  48. data/lib/asciidoctor/pdf/ext/prawn/font_metric_cache.rb +1 -1
  49. data/lib/asciidoctor/pdf/ext/prawn/formatted_text/arranger.rb +33 -3
  50. data/lib/asciidoctor/pdf/ext/prawn/formatted_text/box.rb +25 -14
  51. data/lib/asciidoctor/pdf/ext/prawn/formatted_text/fragment.rb +9 -3
  52. data/lib/asciidoctor/pdf/ext/prawn/formatted_text/protect_bottom_gutter.rb +13 -0
  53. data/lib/asciidoctor/pdf/ext/prawn/images.rb +20 -18
  54. data/lib/asciidoctor/pdf/ext/prawn-svg/loaders/data.rb +6 -0
  55. data/lib/asciidoctor/pdf/ext/prawn-svg/loaders/web.rb +22 -0
  56. data/lib/asciidoctor/pdf/ext/prawn-svg/url_loader.rb +13 -0
  57. data/lib/asciidoctor/pdf/ext/prawn-svg.rb +5 -2
  58. data/lib/asciidoctor/pdf/ext/prawn-table/cell/asciidoc.rb +76 -20
  59. data/lib/asciidoctor/pdf/ext/prawn-table/cell/text.rb +39 -1
  60. data/lib/asciidoctor/pdf/ext/prawn-table/cell.rb +21 -15
  61. data/lib/asciidoctor/pdf/ext/prawn-table.rb +1 -1
  62. data/lib/asciidoctor/pdf/ext/prawn.rb +1 -0
  63. data/lib/asciidoctor/pdf/ext/pygments.rb +2 -2
  64. data/lib/asciidoctor/pdf/ext/rouge/formatters/prawn.rb +17 -20
  65. data/lib/asciidoctor/pdf/ext/rouge/themes/asciidoctor_pdf_default.rb +1 -0
  66. data/lib/asciidoctor/pdf/ext/rouge.rb +0 -1
  67. data/lib/asciidoctor/pdf/formatted_text/formatter.rb +2 -2
  68. data/lib/asciidoctor/pdf/formatted_text/inline_destination_marker.rb +8 -10
  69. data/lib/asciidoctor/pdf/formatted_text/inline_image_arranger.rb +69 -78
  70. data/lib/asciidoctor/pdf/formatted_text/inline_image_renderer.rb +7 -10
  71. data/lib/asciidoctor/pdf/formatted_text/inline_text_aligner.rb +2 -4
  72. data/lib/asciidoctor/pdf/formatted_text/parser.rb +53 -47
  73. data/lib/asciidoctor/pdf/formatted_text/parser.treetop +5 -7
  74. data/lib/asciidoctor/pdf/formatted_text/source_wrap.rb +14 -14
  75. data/lib/asciidoctor/pdf/formatted_text/text_background_and_border_renderer.rb +4 -7
  76. data/lib/asciidoctor/pdf/formatted_text/transform.rb +122 -110
  77. data/lib/asciidoctor/pdf/formatted_text.rb +0 -1
  78. data/lib/asciidoctor/pdf/index_catalog.rb +7 -11
  79. data/lib/asciidoctor/pdf/nogmagick.rb +6 -0
  80. data/lib/asciidoctor/pdf/optimizer.rb +3 -5
  81. data/lib/asciidoctor/pdf/pdfmark.rb +16 -8
  82. data/lib/asciidoctor/pdf/roman_numeral.rb +4 -22
  83. data/lib/asciidoctor/pdf/sanitizer.rb +18 -13
  84. data/lib/asciidoctor/pdf/section_info_by_page.rb +24 -0
  85. data/lib/asciidoctor/pdf/theme_loader.rb +100 -80
  86. data/lib/asciidoctor/pdf/version.rb +1 -2
  87. data/lib/asciidoctor/pdf.rb +5 -2
  88. metadata +36 -64
  89. data/data/fonts/mplus1mn-bold-ascii.ttf +0 -0
  90. data/data/fonts/mplus1mn-bold_italic-ascii.ttf +0 -0
  91. data/data/fonts/mplus1mn-italic-ascii.ttf +0 -0
  92. data/data/fonts/mplus1mn-regular-ascii-conums.ttf +0 -0
  93. data/lib/asciidoctor/pdf/ext/asciidoctor/abstract_block.rb +0 -7
  94. data/lib/asciidoctor/pdf/ext/asciidoctor/abstract_node.rb +0 -7
  95. data/lib/asciidoctor/pdf/ext/asciidoctor/list_item.rb +0 -18
  96. data/lib/asciidoctor/pdf/ext/asciidoctor/logging_shim.rb +0 -33
  97. data/lib/asciidoctor/pdf/ext/core/array.rb +0 -11
  98. data/lib/asciidoctor/pdf/ext/core/hash.rb +0 -7
  99. data/lib/asciidoctor/pdf/ext/core/regexp.rb +0 -5
  100. data/lib/asciidoctor/pdf/ext/pdf-core/pdf_object.rb +0 -8
  101. data/lib/asciidoctor-pdf/converter.rb +0 -3
  102. data/lib/asciidoctor-pdf/version.rb +0 -3
@@ -24,9 +24,5 @@ class Prawn::Font::AFM
24
24
  logger.warn %(The following text could not be fully converted to the Windows-1252 character set:
25
25
  #{text.gsub(/^/, '| ').rstrip}) if logger.info? && !@document.scratch?
26
26
  text.encode 'windows-1252', undef: :replace, replace: ?\u00ac
27
- rescue ::Encoding::InvalidByteSequenceError
28
- raise Prawn::Errors::IncompatibleStringEncoding,
29
- 'Your document includes text which is not compatible with the Windows-1252 character set.
30
- If you need full UTF-8 support, use TTF fonts instead of the built-in PDF (AFM) fonts.'
31
27
  end
32
28
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # TODO: remove when upgrading to prawn-2.5.0
3
4
  class Prawn::FontMetricCache::CacheEntry
4
- # workaround for https://github.com/prawnpdf/prawn/issues/1140
5
5
  def initialize font, options, size
6
6
  font = font.hash
7
7
  super
@@ -1,15 +1,45 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ module Prawn::Text::NoopLstripBang
4
+ def lstrip!; end
5
+ end
6
+
3
7
  Prawn::Text::Formatted::Arranger.prepend (Module.new do
4
8
  def initialize *_args
5
9
  super
6
10
  @dummy_text = ?\u0000
11
+ @normalize_line_height = false
12
+ @sub_and_sup_relative_size = 0.583
13
+ end
14
+
15
+ def format_array= array
16
+ @normalize_line_height = !array.empty? && (array[0].delete :normalize_line_height)
17
+ super
18
+ end
19
+
20
+ def finalize_line
21
+ @consumed.unshift text: Prawn::Text::ZWSP if @normalize_line_height
22
+ super
7
23
  end
8
24
 
9
25
  def next_string
10
- if (string = super) == @dummy_text
11
- def string.lstrip!; end
26
+ (string = super) == @dummy_text ? (string.extend Prawn::Text::NoopLstripBang) : string
27
+ end
28
+
29
+ def apply_font_size size, styles
30
+ if (subscript? styles) || (superscript? styles)
31
+ size ||= @document.font_size
32
+ if String === size
33
+ units = (size.end_with? 'em', '%') ? ((size.end_with? '%') ? '%' : 'em') : ''
34
+ size = %(#{size.to_f * @sub_and_sup_relative_size}#{units})
35
+ else
36
+ size *= @sub_and_sup_relative_size
37
+ end
38
+ @document.font_size(size) { yield }
39
+ elsif size
40
+ @document.font_size(size) { yield }
41
+ else
42
+ yield
12
43
  end
13
- string
14
44
  end
15
45
  end)
@@ -1,7 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Prawn::Text::Formatted::Box.prepend (Module.new do
4
- include ::Asciidoctor::Logging
4
+ include Asciidoctor::Logging
5
+
6
+ def initialize formatted_text, options = {}
7
+ super
8
+ formatted_text[0][:normalize_line_height] = true if options[:normalize_line_height] && !formatted_text.empty?
9
+ options[:extensions]&.each {|extension| extend extension }
10
+ if (bottom_gutter = options[:bottom_gutter]) && bottom_gutter > 0
11
+ @bottom_gutter = bottom_gutter
12
+ extend Prawn::Text::Formatted::ProtectBottomGutter
13
+ end
14
+ end
5
15
 
6
16
  def draw_fragment_overlay_styles fragment
7
17
  if (underline = (styles = fragment.styles).include? :underline) || (styles.include? :strikethrough)
@@ -17,6 +27,7 @@ Prawn::Text::Formatted::Box.prepend (Module.new do
17
27
  end
18
28
  end
19
29
 
30
+ # TODO: remove when upgrading to prawn-2.5.0
20
31
  def analyze_glyphs_for_fallback_font_support fragment_hash
21
32
  fragment_font = fragment_hash[:font] || (original_font = @document.font.family)
22
33
  if (fragment_font_styles = fragment_hash[:styles])
@@ -38,6 +49,7 @@ Prawn::Text::Formatted::Box.prepend (Module.new do
38
49
  form_fragments_from_like_font_glyph_pairs font_glyph_pairs, fragment_hash
39
50
  end
40
51
 
52
+ # TODO: remove once Prawn 2.5 is released
41
53
  def find_font_for_this_glyph char, current_font, current_font_opts = {}, fallback_fonts_to_check = [], original_font = current_font
42
54
  (doc = @document).font current_font, current_font_opts
43
55
  if doc.font.glyph_present? char
@@ -49,7 +61,7 @@ Prawn::Text::Formatted::Box.prepend (Module.new do
49
61
  (doc.instance_variable_get :@missing_chars) : (doc.instance_variable_set :@missing_chars, {})
50
62
  previous_fonts_checked = (missing_chars[char] ||= [])
51
63
  if previous_fonts_checked.empty? && !(previous_fonts_checked.include? fonts_checked)
52
- logger.warn %(Could not locate the character `#{char}' in the following fonts: #{fonts_checked.join ', '})
64
+ logger.warn %(Could not locate the character `#{char}' (#{char.unpack('U*').map {|it| "\\u#{(it.to_s 16).rjust 4, '0'}" }.join}) in the following fonts: #{fonts_checked.join ', '})
53
65
  previous_fonts_checked << fonts_checked
54
66
  end
55
67
  end
@@ -59,29 +71,28 @@ Prawn::Text::Formatted::Box.prepend (Module.new do
59
71
  end
60
72
  end
61
73
 
74
+ # Override method in super class to provide support for a tuple consisting of alignment and offset
62
75
  def process_vertical_alignment text
63
- return super if ::Symbol === (valign = @vertical_align)
76
+ return super if Symbol === (valign = @vertical_align)
64
77
 
65
78
  return if defined? @vertical_alignment_processed
66
79
  @vertical_alignment_processed = true
67
80
 
68
81
  valign, offset = valign
69
82
 
70
- if valign == :top
71
- @at[1] -= offset
72
- return
73
- end
74
-
75
- wrap text
76
- h = height
77
-
78
83
  case valign
79
84
  when :center
80
- @at[1] -= (@height - h + @descender) * 0.5 + offset
85
+ wrap text
86
+ @at[1] -= (@height - (rendered_height = height) + @descender) * 0.5 + offset
87
+ @height = rendered_height
81
88
  when :bottom
82
- @at[1] -= (@height - h) + offset
89
+ wrap text
90
+ @at[1] -= (@height - (rendered_height = height)) + offset
91
+ @height = rendered_height
92
+ else # :top
93
+ @at[1] -= offset
83
94
  end
84
95
 
85
- @height = h
96
+ nil
86
97
  end
87
98
  end)
@@ -3,9 +3,10 @@
3
3
  Prawn::Text::Formatted::Fragment.prepend (Module.new do
4
4
  attr_reader :document
5
5
 
6
- # Prevent fragment from being written by discarding the text.
7
- def conceal
6
+ # Prevent fragment from being written by discarding the text, optionally forcing the width to 0
7
+ def conceal force_width_to_zero = false
8
8
  @text = ''
9
+ @width = 0 if force_width_to_zero
9
10
  end
10
11
 
11
12
  # Don't strip soft hyphens when repacking unretrieved fragments
@@ -14,6 +15,11 @@ Prawn::Text::Formatted::Fragment.prepend (Module.new do
14
15
  super
15
16
  end
16
17
 
18
+ # Use .tr instead of .gsub to remove zero-width spaces
19
+ def strip_zero_width_spaces string
20
+ string.encoding == Encoding::UTF_8 ? (string.tr Prawn::Text::ZWSP, '') : string
21
+ end
22
+
17
23
  # Modify the built-in ascender write method to allow an override value to be
18
24
  # specified using the format_state hash.
19
25
  def ascender= val
@@ -28,7 +34,7 @@ Prawn::Text::Formatted::Fragment.prepend (Module.new do
28
34
 
29
35
  def width
30
36
  if (val = format_state[:width])
31
- val = (val.end_with? 'em') ? val.to_f * @document.font_size : (@document.str_to_pt val) if ::String === val
37
+ val = (val.end_with? 'em') ? val.to_f * @document.font_size : (@document.str_to_pt val) if String === val
32
38
  else
33
39
  val = super
34
40
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn::Text::Formatted::ProtectBottomGutter
4
+ def enough_height_for_this_line?
5
+ return super unless @arranger.finished?
6
+ begin
7
+ @height -= @bottom_gutter
8
+ super
9
+ ensure
10
+ @height += @bottom_gutter
11
+ end
12
+ end
13
+ end
@@ -3,12 +3,6 @@
3
3
  module Asciidoctor
4
4
  module Prawn
5
5
  module Images
6
- class << self
7
- def extended base
8
- base.class.__send__ :alias_method, :_initial_image, :image
9
- end
10
- end
11
-
12
6
  # Dispatch to suitable image method in Prawn based on file extension.
13
7
  def image file, opts = {}
14
8
  # FIXME: handle case when SVG is an IO object
@@ -18,38 +12,46 @@ module Asciidoctor
18
12
  #opts[:enable_web_requests] = allow_uri_read if !(opts.key? :enable_web_requests) && (respond_to? :allow_uri_read)
19
13
  #opts[:cache_images] = cache_uri if !(opts.key? :cache_images) && (respond_to? :cache_uri)
20
14
  #opts[:fallback_font_name] = fallback_svg_font_name if !(opts.key? :fallback_font_name) && (respond_to? :fallback_svg_font_name)
21
- if (opts.key? :fit) && (fit = opts.delete :fit) && !opts[:width] && !opts[:height]
22
- svg (::File.read file, mode: 'r:UTF-8'), opts do |svg_doc|
23
- max_width, max_height = fit
24
- svg_doc.calculate_sizing requested_width: max_width if max_width && svg_doc.sizing.output_width != max_width
25
- svg_doc.calculate_sizing requested_height: max_height if max_height && svg_doc.sizing.output_height > max_height
15
+ if (fit = opts.delete :fit) && !(opts[:width] || opts[:height])
16
+ image_info = svg (::File.read file, mode: 'r:UTF-8'), opts do |svg_doc|
17
+ # NOTE: fit to specified width, then reduce size if height exceeds bounds
18
+ svg_doc.calculate_sizing requested_width: fit[0] if svg_doc.sizing.output_width != fit[0]
19
+ svg_doc.calculate_sizing requested_height: fit[1] if svg_doc.sizing.output_height > fit[1]
26
20
  end
27
21
  else
28
- svg (::File.read file, mode: 'r:UTF-8'), opts
22
+ image_info = svg (::File.read file, mode: 'r:UTF-8'), opts
23
+ end
24
+ if ::Asciidoctor::Logging === self && !scratch? && !(warnings = image_info[:warnings]).empty?
25
+ warnings.each {|warning| log :warn, %(problem encountered in image: #{file}; #{warning}) }
29
26
  end
27
+ image_info
30
28
  else
31
- ::File.open(file, 'rb') {|fd| _initial_image fd, opts }
29
+ ::File.open(file, 'rb') {|fd| super fd, opts }
32
30
  end
33
31
  else
34
- _initial_image file, opts
32
+ super
35
33
  end
36
34
  end
37
35
 
38
- # Retrieve the intrinsic image dimensions for the specified path.
36
+ # Retrieve the intrinsic image dimensions for the specified path in pt.
39
37
  #
40
38
  # Returns a Hash containing :width and :height keys that map to the image's
41
- # intrinsic width and height values (in pixels)
39
+ # intrinsic width and height values (in pt).
42
40
  def intrinsic_image_dimensions path, format
43
41
  if format == 'svg'
42
+ # NOTE: prawn-svg computes intrinsic width and height in pt
44
43
  img_obj = ::Prawn::SVG::Interface.new (::File.read path, mode: 'r:UTF-8'), self, {}
45
44
  img_size = img_obj.document.sizing
46
45
  { width: img_size.output_width, height: img_size.output_height }
47
46
  else
48
- # NOTE build_image_object caches image data previously loaded
47
+ # NOTE: build_image_object caches image data previously loaded
48
+ # NOTE: build_image_object computes intrinsic width and height in px
49
49
  _, img_size = ::File.open(path, 'rb') {|fd| build_image_object fd }
50
- { width: img_size.width, height: img_size.height }
50
+ { width: (to_pt img_size.width, :px), height: (to_pt img_size.height, :px) }
51
51
  end
52
52
  rescue
53
+ # NOTE: image cannot be read, so it won't be used anyway
54
+ { width: 0, height: 0 }
53
55
  end
54
56
  end
55
57
 
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Prawn::SVG::Loaders::Data
4
+ remove_const :REGEXP
5
+ REGEXP = %r(\Adata:image/(?:png|jpe?g);base64(?:;[a-z0-9]+)*,)i
6
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ Prawn::SVG::Loaders::Web.prepend (Module.new do
4
+ def initialize open_uri_loader = nil
5
+ @open_uri_loader = Proc === open_uri_loader ? open_uri_loader : nil
6
+ end
7
+
8
+ def from_url url
9
+ (url.to_s.start_with? 'http://', 'https://') ? (load_open_uri.open_uri url, 'rb', &:read) : nil
10
+ rescue
11
+ raise Prawn::SVG::UrlLoader::Error, $!.message
12
+ end
13
+
14
+ def load_open_uri
15
+ if @open_uri_loader
16
+ @open_uri_loader.call
17
+ else
18
+ require 'open-uri' unless defined? OpenURI
19
+ OpenURI
20
+ end
21
+ end
22
+ end)
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ Prawn::SVG::UrlLoader.prepend (Module.new do
4
+ def initialize enable_cache: false, enable_web: true, enable_file_with_root: nil
5
+ @url_cache = {}
6
+ @enable_cache = enable_cache
7
+ loaders = []
8
+ loaders << Prawn::SVG::Loaders::Data.new
9
+ loaders << (Prawn::SVG::Loaders::Web.new enable_web) if enable_web
10
+ loaders << (Prawn::SVG::Loaders::File.new enable_file_with_root) if enable_file_with_root
11
+ @loaders = loaders
12
+ end
13
+ end)
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'prawn-svg' unless defined? Prawn::SVG::Interface
4
- # NOTE disable system fonts since they're non-portable
3
+ require 'prawn-svg'
4
+ require_relative 'prawn-svg/loaders/data'
5
+ require_relative 'prawn-svg/loaders/web'
6
+ require_relative 'prawn-svg/url_loader'
7
+ # NOTE: disable system fonts since they're non-portable
5
8
  Prawn::SVG::Interface.font_path.clear
@@ -4,6 +4,8 @@ module Prawn
4
4
  class Table
5
5
  class Cell
6
6
  class AsciiDoc < Cell
7
+ include ::Asciidoctor::Logging
8
+
7
9
  attr_accessor :align
8
10
  attr_accessor :valign
9
11
 
@@ -31,45 +33,99 @@ module Prawn
31
33
  # NOTE: automatic image sizing only works if cell has fixed width
32
34
  def dry_run
33
35
  cell = self
34
- max_height = nil
35
- height, = @pdf.dry_run do
36
- max_height = bounds.height
37
- # NOTE: we should be able to use cell.max_width, but returns 0 in some conditions (like when colspan > 1)
38
- indent cell.padding_left, bounds.width - cell.width + cell.padding_right do
39
- # HACK: force margin_top to be applied
40
- move_down 0.0001
41
- # TODO: truncate margin bottom of last block
42
- traverse cell.content
36
+ parent_doc = (doc = content.document).nested? ? doc.parent_document : doc
37
+ padding_y = cell.padding_top + cell.padding_bottom
38
+ max_height = @pdf.bounds.height
39
+ extent = nil
40
+ apply_font_properties do
41
+ extent = @pdf.dry_run keep_together: true, single_page: true do
42
+ push_scratch parent_doc
43
+ doc.catalog[:footnotes] = parent_doc.catalog[:footnotes]
44
+ if padding_y > 0
45
+ move_down padding_y
46
+ #elsif at_page_top?
47
+ else
48
+ # TODO: encapsulate this logic to force top margin to be applied
49
+ margin_box.instance_variable_set :@y, margin_box.absolute_top + 0.0001
50
+ end
51
+ # NOTE: we should be able to use cell.max_width, but returns 0 in some conditions (like when colspan > 1)
52
+ indent cell.padding_left, bounds.width - cell.width + cell.padding_right do
53
+ # TODO: truncate margin bottom of last block
54
+ traverse cell.content
55
+ end
56
+ pop_scratch parent_doc
57
+ doc.catalog[:footnotes] = parent_doc.catalog[:footnotes]
43
58
  end
44
59
  end
45
- # FIXME: prawn-table doesn't support cell taller than a single page
46
- [max_height, height - 0.0001].min
60
+ # NOTE: prawn-table doesn't support cells that exceed the height of a single page
61
+ # NOTE: height does not include top/bottom padding, but must account for it when checking for overrun
62
+ (extent.single_page_height || max_height) - padding_y
47
63
  end
48
64
 
49
65
  def natural_content_width
50
- # QUESTION can we get a better estimate of the natural width?
66
+ # QUESTION: can we get a better estimate of the natural width?
51
67
  @natural_content_width ||= (@pdf.bounds.width - padding_left - padding_right)
52
68
  end
53
69
 
54
70
  def natural_content_height
55
- # NOTE when natural_content_height is called, we already know max width
71
+ # NOTE: when natural_content_height is called, we already know max width
56
72
  @natural_content_height ||= dry_run
57
73
  end
58
74
 
75
+ # NOTE: prawn-table doesn't support cells that exceed the height of a single page
59
76
  def draw_content
60
- pdf = @pdf
61
- # NOTE draw_bounded_content automatically adds FPTolerance to width and height
77
+ if (pdf = @pdf).scratch?
78
+ pdf.move_down natural_content_height
79
+ return
80
+ end
81
+ # NOTE: draw_bounded_content automatically adds FPTolerance to width and height
62
82
  pdf.bounds.instance_variable_set :@width, spanned_content_width
63
- # NOTE we've already reserved the space, so just let the box stretch to the bottom of the page to avoid overflow
64
- pdf.bounds.instance_variable_set :@height, pdf.y
83
+ padding_adjustment = content.context == :document ? padding_bottom : 0
84
+ # NOTE: we've already reserved the space, so just let the box stretch to bottom of the content area
85
+ pdf.bounds.instance_variable_set :@height, (pdf.y - pdf.page.margins[:bottom] - padding_adjustment)
65
86
  if @valign != :top && (excess_y = spanned_content_height - natural_content_height) > 0
87
+ # QUESTION: could this cause a unexpected page overrun?
66
88
  pdf.move_down(@valign == :center ? (excess_y.fdiv 2) : excess_y)
67
89
  end
68
- # TODO: apply horizontal alignment (right now must use alignment on content block)
69
- # QUESTION inherit table cell font properties?
70
- pdf.traverse content
90
+ # # use perform_on_single_page to prevent content from being written on extra pages
91
+ # # the problem with this approach is that we don't know whether any content is written to next page
92
+ # apply_font_properties do
93
+ # if (pdf.perform_on_single_page { pdf.traverse content })
94
+ # logger.error %(the table cell on page #{pdf.page_number} has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page)
95
+ # end
96
+ # end
97
+ start_page = pdf.page_number
98
+ # TODO: apply horizontal alignment; currently it is necessary to specify alignment on content blocks
99
+ apply_font_properties { pdf.traverse content }
100
+ if (extra_pages = pdf.page_number - start_page) > 0
101
+ logger.error %(the table cell on page #{start_page} has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page) unless extra_pages == 1 && pdf.page.empty?
102
+ extra_pages.times { pdf.delete_page }
103
+ end
71
104
  nil
72
105
  end
106
+
107
+ private
108
+
109
+ def apply_font_properties
110
+ # NOTE: font_info holds font properties outside table; used as fallback values
111
+ # QUESTION: should we inherit table cell font properties?
112
+ font_info = (pdf = @pdf).font_info
113
+ font_color, font_family, font_size, font_style = @font_options.values_at :color, :family, :size, :style
114
+ prev_font_color, pdf.font_color = pdf.font_color, font_color if font_color
115
+ font_family ||= font_info[:family]
116
+ if font_size
117
+ prev_font_scale, pdf.font_scale = pdf.font_scale, (font_size.to_f / @pdf.root_font_size)
118
+ else
119
+ font_size = font_info[:size]
120
+ end
121
+ font_style ||= font_info[:style]
122
+ pdf.font font_family, size: font_size, style: font_style do
123
+ yield
124
+ ensure
125
+ pdf.font_color = prev_font_color if prev_font_color
126
+ pdf.font_scale = prev_font_scale if prev_font_scale
127
+ end
128
+ end
73
129
  end
74
130
  end
75
131
  end
@@ -1,16 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Prawn::Table::Cell::Text
4
+ include ::Asciidoctor::Logging
5
+
6
+ ImageTagRx = /<img(?: [^>]+ )?width="([^"]+)"[^>]*>/
7
+
4
8
  # Override draw_content method to drop cursor advancement
5
9
  remove_method :draw_content
6
10
  def draw_content
7
11
  with_font do
8
- with_text_color do
12
+ self.valign = [:center, -font.descender * 0.5] if valign == :center
13
+ remaining_text = with_text_color do
9
14
  (text_box \
10
15
  width: spanned_content_width + FPTolerance,
11
16
  height: spanned_content_height + FPTolerance,
12
17
  at: [0, @pdf.cursor]).render
13
18
  end
19
+ logger.error %(the table cell on page #{@pdf.page_number} has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page) unless remaining_text.empty? || @pdf.scratch?
20
+ end
21
+ end
22
+
23
+ # Override the styled_width_of to account for image widths and hard line breaks.
24
+ # This method computes the width of the text without wrapping (so InlineImageArranger is not called).
25
+ # This override also effectively backports the fix for prawn-table#42.
26
+ remove_method :styled_width_of
27
+ def styled_width_of text
28
+ # NOTE: remove :style since it's handled by with_font
29
+ options = @text_options.reject {|k| k == :style }
30
+ width_of_images = 0
31
+ if (inline_format = @text_options.key? :inline_format) && (text.include? '<img ')
32
+ placeholder_width = styled_width_of 'M'
33
+ text = text.gsub ImageTagRx do
34
+ if (pctidx = $1.index '%')
35
+ if pctidx == $1.length - 1
36
+ # TODO: look up the intrinsic image width in pixels
37
+ #width_of_images += (<image width> - placeholder_width)
38
+ next ''
39
+ else
40
+ width_of_images += (($1.slice pctidx + 1, $1.length).to_f - placeholder_width)
41
+ end
42
+ else
43
+ width_of_images += ($1.to_f - placeholder_width)
44
+ end
45
+ 'M'
46
+ end
47
+ end
48
+ if inline_format && text.length > 3 && (text.include? '<br>')
49
+ (text.split '<br>').map {|line| (line = line.strip).empty? ? 0 : with_font { @pdf.width_of line, options } }.max + width_of_images
50
+ else
51
+ with_font { @pdf.width_of text, options } + width_of_images
14
52
  end
15
53
  end
16
54
  end
@@ -1,12 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Prawn::Table::Cell
4
- remove_method :draw_borders
3
+ Prawn::Table::Cell.prepend (Module.new do
4
+ def border_color= color
5
+ color = [color, color] if Asciidoctor::PDF::ThemeLoader::CMYKColorValue === color
6
+ super
7
+ end
8
+
5
9
  # Draws borders around the cell. Borders are centered on the bounds of
6
10
  # the cell outside of any padding, so the caller is responsible for
7
11
  # setting appropriate padding to ensure the border does not overlap with
8
12
  # cell content.
9
13
  #
14
+ # Adds support for transparent border color.
15
+ #
10
16
  def draw_borders pt
11
17
  x, y = pt
12
18
 
@@ -21,26 +27,26 @@ class Prawn::Table::Cell
21
27
 
22
28
  # Left and right borders are drawn one-half border beyond the center
23
29
  # of the corner, so that the corners end up square.
24
- from, to = case border
25
- when :top
26
- [[x, y], [x + width, y]]
27
- when :bottom
28
- [[x, y - height], [x + width, y - height]]
29
- when :left
30
- [[x, y + (border_top_width / 2.0)], [x, y - height - (border_bottom_width / 2.0)]]
31
- when :right
32
- [[x + width, y + (border_top_width / 2.0)], [x + width, y - height - (border_bottom_width / 2.0)]]
33
- end
30
+ case border
31
+ when :top
32
+ from, to = [[x, y], [x + width, y]]
33
+ when :bottom
34
+ from, to = [[x, y - height], [x + width, y - height]]
35
+ when :left
36
+ from, to = [[x, y + (border_top_width / 2.0)], [x, y - height - (border_bottom_width / 2.0)]]
37
+ else # :right
38
+ from, to = [[x + width, y + (border_top_width / 2.0)], [x + width, y - height - (border_bottom_width / 2.0)]]
39
+ end
34
40
 
35
41
  case border_line
36
42
  when :dashed
37
43
  @pdf.dash border_width * 4
38
44
  when :dotted
39
- @pdf.dash border_width, space: border_width * 2
45
+ @pdf.dash border_width
40
46
  when :solid
41
47
  # normal line style
42
48
  else
43
- raise ::ArgumentError, 'border_line must be :solid, :dotted or :dashed'
49
+ raise ArgumentError, 'border_line must be :solid, :dotted or :dashed'
44
50
  end
45
51
 
46
52
  @pdf.line_width = border_width
@@ -57,4 +63,4 @@ class Prawn::Table::Cell
57
63
  end
58
64
  end
59
65
  end
60
- end
66
+ end)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require ENV['PRAWN_TABLE_REQUIRE_PATH'] || 'prawn/table' unless defined? Prawn::Table::VERSION
3
+ require 'prawn/table'
4
4
  require_relative 'prawn-table/cell'
5
5
  require_relative 'prawn-table/cell/asciidoc'
6
6
  require_relative 'prawn-table/cell/text'
@@ -7,4 +7,5 @@ require_relative 'prawn/images'
7
7
  require_relative 'prawn/formatted_text/arranger'
8
8
  require_relative 'prawn/formatted_text/box'
9
9
  require_relative 'prawn/formatted_text/fragment'
10
+ require_relative 'prawn/formatted_text/protect_bottom_gutter'
10
11
  require_relative 'prawn/extensions'
@@ -4,8 +4,8 @@ module Pygments
4
4
  module Ext
5
5
  module BlockStyles
6
6
  BlockSelectorRx = /^\.highlight *\{([^}]+?)\}/
7
- HighlightBackgroundColorRx = /^\.highlight +\.hll +{ *background(?:-color)?: *#([a-fA-F0-9]{6})/
8
- ColorPropertiesRx = /(?:^|;) *(background(?:-color)?|color): *#?([a-fA-F0-9]{6}) *(?=$|;)/
7
+ HighlightBackgroundColorRx = /^\.highlight +\.hll +{ *background(?:-color)?: *#(\h{6})/
8
+ ColorPropertiesRx = /(?:^|;) *(background(?:-color)?|color): *#?(\h{6}) *(?=$|;)/
9
9
 
10
10
  @cache = ::Hash.new do |cache, key|
11
11
  styles = {}