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.
- checksums.yaml +4 -4
- data/.yardopts +1 -1
- data/CHANGELOG.adoc +273 -31
- data/NOTICE.adoc +16 -4
- data/README.adoc +208 -68
- data/asciidoctor-pdf.gemspec +3 -7
- data/data/fonts/ABOUT-mplus1mn-subset +1 -1
- data/data/fonts/ABOUT-mplus1p-subset +2 -2
- data/data/fonts/ABOUT-notosans-subset +26 -0
- data/data/fonts/ABOUT-notoserif-subset +1 -1
- data/data/fonts/{LICENSE-notoserif → LICENSE-noto} +0 -0
- data/data/fonts/mplus1mn-bold-subset.ttf +0 -0
- data/data/fonts/mplus1mn-bold_italic-subset.ttf +0 -0
- data/data/fonts/mplus1mn-italic-subset.ttf +0 -0
- data/data/fonts/mplus1mn-regular-subset.ttf +0 -0
- data/data/fonts/mplus1p-regular-fallback.ttf +0 -0
- data/data/fonts/notoemoji-subset.ttf +0 -0
- data/data/fonts/notosans-bold-subset.ttf +0 -0
- data/data/fonts/notosans-bold_italic-subset.ttf +0 -0
- data/data/fonts/notosans-italic-subset.ttf +0 -0
- data/data/fonts/notosans-regular-subset.ttf +0 -0
- data/data/fonts/notoserif-bold-subset.ttf +0 -0
- data/data/fonts/notoserif-bold_italic-subset.ttf +0 -0
- data/data/fonts/notoserif-italic-subset.ttf +0 -0
- data/data/fonts/notoserif-regular-subset.ttf +0 -0
- data/data/themes/base-theme.yml +21 -24
- data/data/themes/default-for-print-theme.yml +24 -0
- data/data/themes/default-for-print-with-fallback-font-theme.yml +3 -0
- data/data/themes/default-theme.yml +55 -59
- data/data/themes/default-with-fallback-font-theme.yml +2 -2
- data/data/themes/sans-with-fallback-font-theme.yml +10 -0
- data/docs/theming-guide.adoc +977 -352
- data/lib/asciidoctor/pdf/converter.rb +1853 -1566
- data/lib/asciidoctor/pdf/ext/asciidoctor/document.rb +22 -1
- data/lib/asciidoctor/pdf/ext/asciidoctor/image.rb +9 -15
- data/lib/asciidoctor/pdf/ext/asciidoctor/list.rb +6 -13
- data/lib/asciidoctor/pdf/ext/asciidoctor/section.rb +3 -16
- data/lib/asciidoctor/pdf/ext/asciidoctor.rb +1 -5
- data/lib/asciidoctor/pdf/ext/core/file.rb +1 -1
- data/lib/asciidoctor/pdf/ext/core/quantifiable_stdout.rb +1 -4
- data/lib/asciidoctor/pdf/ext/core/string.rb +2 -2
- data/lib/asciidoctor/pdf/ext/core.rb +1 -4
- data/lib/asciidoctor/pdf/ext/pdf-core/page.rb +8 -33
- data/lib/asciidoctor/pdf/ext/pdf-core.rb +0 -16
- data/lib/asciidoctor/pdf/ext/prawn/coderay_encoder.rb +5 -7
- data/lib/asciidoctor/pdf/ext/prawn/extensions.rb +489 -331
- data/lib/asciidoctor/pdf/ext/prawn/font/afm.rb +0 -4
- data/lib/asciidoctor/pdf/ext/prawn/font_metric_cache.rb +1 -1
- data/lib/asciidoctor/pdf/ext/prawn/formatted_text/arranger.rb +33 -3
- data/lib/asciidoctor/pdf/ext/prawn/formatted_text/box.rb +25 -14
- data/lib/asciidoctor/pdf/ext/prawn/formatted_text/fragment.rb +9 -3
- data/lib/asciidoctor/pdf/ext/prawn/formatted_text/protect_bottom_gutter.rb +13 -0
- data/lib/asciidoctor/pdf/ext/prawn/images.rb +20 -18
- data/lib/asciidoctor/pdf/ext/prawn-svg/loaders/data.rb +6 -0
- data/lib/asciidoctor/pdf/ext/prawn-svg/loaders/web.rb +22 -0
- data/lib/asciidoctor/pdf/ext/prawn-svg/url_loader.rb +13 -0
- data/lib/asciidoctor/pdf/ext/prawn-svg.rb +5 -2
- data/lib/asciidoctor/pdf/ext/prawn-table/cell/asciidoc.rb +76 -20
- data/lib/asciidoctor/pdf/ext/prawn-table/cell/text.rb +39 -1
- data/lib/asciidoctor/pdf/ext/prawn-table/cell.rb +21 -15
- data/lib/asciidoctor/pdf/ext/prawn-table.rb +1 -1
- data/lib/asciidoctor/pdf/ext/prawn.rb +1 -0
- data/lib/asciidoctor/pdf/ext/pygments.rb +2 -2
- data/lib/asciidoctor/pdf/ext/rouge/formatters/prawn.rb +17 -20
- data/lib/asciidoctor/pdf/ext/rouge/themes/asciidoctor_pdf_default.rb +1 -0
- data/lib/asciidoctor/pdf/ext/rouge.rb +0 -1
- data/lib/asciidoctor/pdf/formatted_text/formatter.rb +2 -2
- data/lib/asciidoctor/pdf/formatted_text/inline_destination_marker.rb +8 -10
- data/lib/asciidoctor/pdf/formatted_text/inline_image_arranger.rb +69 -78
- data/lib/asciidoctor/pdf/formatted_text/inline_image_renderer.rb +7 -10
- data/lib/asciidoctor/pdf/formatted_text/inline_text_aligner.rb +2 -4
- data/lib/asciidoctor/pdf/formatted_text/parser.rb +53 -47
- data/lib/asciidoctor/pdf/formatted_text/parser.treetop +5 -7
- data/lib/asciidoctor/pdf/formatted_text/source_wrap.rb +14 -14
- data/lib/asciidoctor/pdf/formatted_text/text_background_and_border_renderer.rb +4 -7
- data/lib/asciidoctor/pdf/formatted_text/transform.rb +122 -110
- data/lib/asciidoctor/pdf/formatted_text.rb +0 -1
- data/lib/asciidoctor/pdf/index_catalog.rb +7 -11
- data/lib/asciidoctor/pdf/nogmagick.rb +6 -0
- data/lib/asciidoctor/pdf/optimizer.rb +3 -5
- data/lib/asciidoctor/pdf/pdfmark.rb +16 -8
- data/lib/asciidoctor/pdf/roman_numeral.rb +4 -22
- data/lib/asciidoctor/pdf/sanitizer.rb +18 -13
- data/lib/asciidoctor/pdf/section_info_by_page.rb +24 -0
- data/lib/asciidoctor/pdf/theme_loader.rb +100 -80
- data/lib/asciidoctor/pdf/version.rb +1 -2
- data/lib/asciidoctor/pdf.rb +5 -2
- metadata +36 -64
- data/data/fonts/mplus1mn-bold-ascii.ttf +0 -0
- data/data/fonts/mplus1mn-bold_italic-ascii.ttf +0 -0
- data/data/fonts/mplus1mn-italic-ascii.ttf +0 -0
- data/data/fonts/mplus1mn-regular-ascii-conums.ttf +0 -0
- data/lib/asciidoctor/pdf/ext/asciidoctor/abstract_block.rb +0 -7
- data/lib/asciidoctor/pdf/ext/asciidoctor/abstract_node.rb +0 -7
- data/lib/asciidoctor/pdf/ext/asciidoctor/list_item.rb +0 -18
- data/lib/asciidoctor/pdf/ext/asciidoctor/logging_shim.rb +0 -33
- data/lib/asciidoctor/pdf/ext/core/array.rb +0 -11
- data/lib/asciidoctor/pdf/ext/core/hash.rb +0 -7
- data/lib/asciidoctor/pdf/ext/core/regexp.rb +0 -5
- data/lib/asciidoctor/pdf/ext/pdf-core/pdf_object.rb +0 -8
- data/lib/asciidoctor-pdf/converter.rb +0 -3
- 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,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
|
-
|
11
|
-
|
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
|
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
|
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
|
-
|
85
|
+
wrap text
|
86
|
+
@at[1] -= (@height - (rendered_height = height) + @descender) * 0.5 + offset
|
87
|
+
@height = rendered_height
|
81
88
|
when :bottom
|
82
|
-
|
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
|
-
|
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
|
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 (
|
22
|
-
svg (::File.read file, mode: 'r:UTF-8'), opts do |svg_doc|
|
23
|
-
|
24
|
-
svg_doc.calculate_sizing requested_width:
|
25
|
-
svg_doc.calculate_sizing requested_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|
|
29
|
+
::File.open(file, 'rb') {|fd| super fd, opts }
|
32
30
|
end
|
33
31
|
else
|
34
|
-
|
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
|
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,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'
|
4
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
#
|
46
|
-
|
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
|
-
|
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
|
-
|
64
|
-
|
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
|
-
#
|
69
|
-
#
|
70
|
-
|
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
|
-
|
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
|
-
|
4
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
45
|
+
@pdf.dash border_width
|
40
46
|
when :solid
|
41
47
|
# normal line style
|
42
48
|
else
|
43
|
-
raise
|
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
|
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)?: *#(
|
8
|
-
ColorPropertiesRx = /(?:^|;) *(background(?:-color)?|color): *#?(
|
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 = {}
|