asciidoctor-pdf 1.5.0.alpha.16 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.yardopts +12 -0
- data/CHANGELOG.adoc +415 -1
- data/LICENSE.adoc +1 -1
- data/NOTICE.adoc +14 -11
- data/README.adoc +647 -222
- data/asciidoctor-pdf.gemspec +47 -44
- data/bin/asciidoctor-pdf +5 -9
- data/bin/asciidoctor-pdf-optimize +20 -0
- data/data/fonts/ABOUT-mplus1mn-subset +26 -0
- data/data/fonts/ABOUT-mplus1p-subset +26 -0
- data/data/fonts/ABOUT-notoemoji-subset +3 -0
- data/data/fonts/ABOUT-notoserif-subset +26 -0
- data/data/fonts/{LICENSE-mplus-testflight-58 → LICENSE-mplus} +2 -2
- data/data/fonts/{LICENSE-noto-2015-06-05 → LICENSE-notoserif} +0 -0
- data/data/fonts/mplus1mn-bold-ascii.ttf +0 -0
- data/data/fonts/mplus1mn-bold-subset.ttf +0 -0
- data/data/fonts/mplus1mn-bold_italic-ascii.ttf +0 -0
- data/data/fonts/mplus1mn-bold_italic-subset.ttf +0 -0
- data/data/fonts/mplus1mn-italic-ascii.ttf +0 -0
- data/data/fonts/mplus1mn-italic-subset.ttf +0 -0
- data/data/fonts/mplus1mn-regular-ascii-conums.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/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 +26 -4
- data/data/themes/default-theme.yml +76 -60
- data/data/themes/default-with-fallback-font-theme.yml +9 -0
- data/docs/theming-guide.adoc +2731 -922
- data/lib/asciidoctor/pdf/converter.rb +4489 -0
- data/lib/asciidoctor/pdf/ext/asciidoctor/abstract_block.rb +7 -0
- data/lib/asciidoctor/pdf/ext/asciidoctor/abstract_node.rb +7 -0
- data/lib/asciidoctor/pdf/ext/asciidoctor/document.rb +5 -0
- data/lib/asciidoctor/pdf/ext/asciidoctor/image.rb +35 -0
- data/lib/{asciidoctor-pdf/asciidoctor_ext → asciidoctor/pdf/ext/asciidoctor}/list.rb +4 -2
- data/lib/{asciidoctor-pdf/asciidoctor_ext → asciidoctor/pdf/ext/asciidoctor}/list_item.rb +3 -1
- data/lib/asciidoctor/pdf/ext/asciidoctor/logging_shim.rb +33 -0
- data/lib/asciidoctor/pdf/ext/asciidoctor/section.rb +45 -0
- data/lib/asciidoctor/pdf/ext/asciidoctor.rb +11 -0
- data/lib/{asciidoctor-pdf/core_ext → asciidoctor/pdf/ext/core}/array.rb +6 -6
- data/lib/asciidoctor/pdf/ext/core/file.rb +9 -0
- data/lib/asciidoctor/pdf/ext/core/hash.rb +7 -0
- data/lib/asciidoctor/pdf/ext/core/numeric.rb +26 -0
- data/lib/{asciidoctor-pdf/core_ext → asciidoctor/pdf/ext/core}/object.rb +3 -1
- data/lib/{asciidoctor-pdf/core_ext → asciidoctor/pdf/ext/core}/quantifiable_stdout.rb +9 -1
- data/lib/asciidoctor/pdf/ext/core/regexp.rb +5 -0
- data/lib/{asciidoctor-pdf/core_ext → asciidoctor/pdf/ext/core}/string.rb +9 -13
- data/lib/asciidoctor/pdf/ext/core.rb +10 -0
- data/lib/asciidoctor/pdf/ext/pdf-core/page.rb +54 -0
- data/lib/asciidoctor/pdf/ext/pdf-core/pdf_object.rb +8 -0
- data/lib/asciidoctor/pdf/ext/pdf-core.rb +4 -0
- data/lib/asciidoctor/pdf/ext/prawn/coderay_encoder.rb +117 -0
- data/lib/asciidoctor/pdf/ext/prawn/extensions.rb +922 -0
- data/lib/{asciidoctor-pdf/prawn_ext → asciidoctor/pdf/ext/prawn}/font/afm.rb +14 -10
- data/lib/asciidoctor/pdf/ext/prawn/font_metric_cache.rb +9 -0
- data/lib/asciidoctor/pdf/ext/prawn/formatted_text/box.rb +66 -0
- data/lib/{asciidoctor-pdf/prawn_ext → asciidoctor/pdf/ext/prawn}/formatted_text/fragment.rb +21 -18
- data/lib/asciidoctor/pdf/ext/prawn/images.rb +54 -0
- data/lib/asciidoctor/pdf/ext/prawn-svg/interface.rb +14 -0
- data/lib/asciidoctor/pdf/ext/prawn-svg.rb +6 -0
- data/lib/asciidoctor/pdf/ext/prawn-table/cell/asciidoc.rb +76 -0
- data/lib/{asciidoctor-pdf/prawn-table_ext → asciidoctor/pdf/ext/prawn-table}/cell/text.rb +6 -3
- data/lib/asciidoctor/pdf/ext/prawn-table/cell.rb +60 -0
- data/lib/asciidoctor/pdf/ext/prawn-table.rb +6 -0
- data/lib/{asciidoctor-pdf/prawn-templates_ext.rb → asciidoctor/pdf/ext/prawn-templates.rb} +2 -0
- data/lib/asciidoctor/pdf/ext/prawn.rb +9 -0
- data/lib/asciidoctor/pdf/ext/pygments.rb +34 -0
- data/lib/asciidoctor/pdf/ext/rouge/formatters/prawn.rb +208 -0
- data/lib/{asciidoctor-pdf/rouge_ext/themes/pastie.rb → asciidoctor/pdf/ext/rouge/themes/asciidoctor_pdf_default.rb} +7 -5
- data/lib/asciidoctor/pdf/ext/rouge.rb +5 -0
- data/lib/asciidoctor/pdf/ext.rb +9 -0
- data/lib/asciidoctor/pdf/formatted_text/formatter.rb +43 -0
- data/lib/asciidoctor/pdf/formatted_text/fragment_position_renderer.rb +14 -0
- data/lib/asciidoctor/pdf/formatted_text/inline_destination_marker.rb +21 -0
- data/lib/asciidoctor/pdf/formatted_text/inline_image_arranger.rb +134 -0
- data/lib/asciidoctor/pdf/formatted_text/inline_image_renderer.rb +51 -0
- data/lib/asciidoctor/pdf/formatted_text/inline_text_aligner.rb +22 -0
- data/lib/{asciidoctor-pdf → asciidoctor/pdf}/formatted_text/parser.rb +175 -53
- data/lib/{asciidoctor-pdf → asciidoctor/pdf}/formatted_text/parser.treetop +20 -14
- data/lib/asciidoctor/pdf/formatted_text/source_wrap.rb +43 -0
- data/lib/asciidoctor/pdf/formatted_text/text_background_and_border_renderer.rb +55 -0
- data/lib/asciidoctor/pdf/formatted_text/transform.rb +394 -0
- data/lib/{asciidoctor-pdf → asciidoctor/pdf}/formatted_text.rb +6 -0
- data/lib/asciidoctor/pdf/index_catalog.rb +133 -0
- data/lib/asciidoctor/pdf/measurements.rb +62 -0
- data/lib/asciidoctor/pdf/optimizer.rb +44 -0
- data/lib/asciidoctor/pdf/pdfmark.rb +41 -0
- data/lib/asciidoctor/pdf/roman_numeral.rb +128 -0
- data/lib/asciidoctor/pdf/sanitizer.rb +45 -0
- data/lib/asciidoctor/pdf/text_transformer.rb +116 -0
- data/lib/asciidoctor/pdf/theme_loader.rb +305 -0
- data/lib/asciidoctor/pdf/version.rb +8 -0
- data/lib/asciidoctor/pdf.rb +15 -0
- data/lib/asciidoctor-pdf/converter.rb +2 -3343
- data/lib/asciidoctor-pdf/version.rb +3 -5
- data/lib/asciidoctor-pdf.rb +3 -3
- metadata +242 -128
- data/Gemfile +0 -22
- data/Rakefile +0 -81
- data/lib/asciidoctor-pdf/asciidoctor_ext/image.rb +0 -24
- data/lib/asciidoctor-pdf/asciidoctor_ext/section.rb +0 -34
- data/lib/asciidoctor-pdf/asciidoctor_ext.rb +0 -5
- data/lib/asciidoctor-pdf/core_ext/numeric.rb +0 -15
- data/lib/asciidoctor-pdf/core_ext/ostruct.rb +0 -17
- data/lib/asciidoctor-pdf/core_ext.rb +0 -4
- data/lib/asciidoctor-pdf/formatted_text/formatter.rb +0 -27
- data/lib/asciidoctor-pdf/formatted_text/inline_destination_marker.rb +0 -21
- data/lib/asciidoctor-pdf/formatted_text/inline_image_arranger.rb +0 -172
- data/lib/asciidoctor-pdf/formatted_text/inline_image_renderer.rb +0 -46
- data/lib/asciidoctor-pdf/formatted_text/transform.rb +0 -261
- data/lib/asciidoctor-pdf/implicit_header_processor.rb +0 -63
- data/lib/asciidoctor-pdf/index_catalog.rb +0 -119
- data/lib/asciidoctor-pdf/measurements.rb +0 -58
- data/lib/asciidoctor-pdf/pdf-core_ext/page.rb +0 -25
- data/lib/asciidoctor-pdf/pdf-core_ext/pdf_object.rb +0 -6
- data/lib/asciidoctor-pdf/pdf-core_ext.rb +0 -2
- data/lib/asciidoctor-pdf/pdfmark.rb +0 -33
- data/lib/asciidoctor-pdf/prawn-svg_ext/interface.rb +0 -10
- data/lib/asciidoctor-pdf/prawn-svg_ext.rb +0 -4
- data/lib/asciidoctor-pdf/prawn-table_ext/cell/asciidoc.rb +0 -69
- data/lib/asciidoctor-pdf/prawn-table_ext.rb +0 -3
- data/lib/asciidoctor-pdf/prawn_ext/coderay_encoder.rb +0 -115
- data/lib/asciidoctor-pdf/prawn_ext/extensions.rb +0 -863
- data/lib/asciidoctor-pdf/prawn_ext/images.rb +0 -40
- data/lib/asciidoctor-pdf/prawn_ext.rb +0 -5
- data/lib/asciidoctor-pdf/roman_numeral.rb +0 -114
- data/lib/asciidoctor-pdf/rouge_ext/css_theme.rb +0 -15
- data/lib/asciidoctor-pdf/rouge_ext/formatters/prawn.rb +0 -164
- data/lib/asciidoctor-pdf/rouge_ext.rb +0 -4
- data/lib/asciidoctor-pdf/sanitizer.rb +0 -88
- data/lib/asciidoctor-pdf/temporary_path.rb +0 -13
- data/lib/asciidoctor-pdf/theme_loader.rb +0 -247
- data/lib/asciidoctor-pdf/ttfunk_ext.rb +0 -8
@@ -1,10 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Prawn::Font::AFM
|
4
|
+
include ::Asciidoctor::Logging
|
5
|
+
|
2
6
|
FALLBACK_CHARS = {
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
7
|
+
?\u200b => '',
|
8
|
+
?\u202f => ?\u00a0,
|
9
|
+
?\u2009 => ' ',
|
10
|
+
?\u25e6 => '-',
|
11
|
+
?\u25aa => ?\u00b7,
|
8
12
|
}
|
9
13
|
|
10
14
|
remove_method :normalize_encoding
|
@@ -17,12 +21,12 @@ class Prawn::Font::AFM
|
|
17
21
|
def normalize_encoding text
|
18
22
|
text.encode 'windows-1252', fallback: FALLBACK_CHARS
|
19
23
|
rescue ::Encoding::UndefinedConversionError
|
20
|
-
warn
|
21
|
-
|
22
|
-
text.encode 'windows-1252', undef: :replace, replace:
|
24
|
+
logger.warn %(The following text could not be fully converted to the Windows-1252 character set:
|
25
|
+
#{text.gsub(/^/, '| ').rstrip}) if logger.info? && !@document.scratch?
|
26
|
+
text.encode 'windows-1252', undef: :replace, replace: ?\u00ac
|
23
27
|
rescue ::Encoding::InvalidByteSequenceError
|
24
28
|
raise Prawn::Errors::IncompatibleStringEncoding,
|
25
|
-
|
26
|
-
If you need full UTF-8 support, use TTF fonts instead of the built-in PDF (AFM) fonts.
|
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.'
|
27
31
|
end
|
28
32
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Prawn::Text::Formatted::Box.prepend (Module.new do
|
4
|
+
include ::Asciidoctor::Logging
|
5
|
+
|
6
|
+
def draw_fragment_overlay_styles fragment
|
7
|
+
if (underline = (styles = fragment.styles).include? :underline) || (styles.include? :strikethrough)
|
8
|
+
(doc = fragment.document).save_graphics_state do
|
9
|
+
if (text_decoration_width = (fs = fragment.format_state)[:text_decoration_width] || doc.text_decoration_width)
|
10
|
+
doc.line_width = text_decoration_width
|
11
|
+
end
|
12
|
+
if (text_decoration_color = fs[:text_decoration_color])
|
13
|
+
doc.stroke_color = text_decoration_color
|
14
|
+
end
|
15
|
+
underline ? (doc.stroke_line fragment.underline_points) : (doc.stroke_line fragment.strikethrough_points)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_font_for_this_glyph char, current_font, fallback_fonts_to_check, original_font = current_font
|
21
|
+
(doc = @document).font current_font
|
22
|
+
if fallback_fonts_to_check.empty?
|
23
|
+
if logger.info? && !doc.scratch?
|
24
|
+
fonts_checked = @fallback_fonts.dup.unshift original_font
|
25
|
+
missing_chars = (doc.instance_variable_defined? :@missing_chars) ?
|
26
|
+
(doc.instance_variable_get :@missing_chars) : (doc.instance_variable_set :@missing_chars, {})
|
27
|
+
previous_fonts_checked = (missing_chars[char] ||= [])
|
28
|
+
if previous_fonts_checked.empty? && !(previous_fonts_checked.include? fonts_checked)
|
29
|
+
logger.warn %(Could not locate the character `#{char}' in the following fonts: #{fonts_checked.join ', '})
|
30
|
+
previous_fonts_checked << fonts_checked
|
31
|
+
end
|
32
|
+
end
|
33
|
+
current_font
|
34
|
+
elsif doc.font.glyph_present? char
|
35
|
+
current_font
|
36
|
+
else
|
37
|
+
find_font_for_this_glyph char, fallback_fonts_to_check.shift, fallback_fonts_to_check, original_font
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def process_vertical_alignment text
|
42
|
+
return super if ::Symbol === (valign = @vertical_align)
|
43
|
+
|
44
|
+
return if defined? @vertical_alignment_processed
|
45
|
+
@vertical_alignment_processed = true
|
46
|
+
|
47
|
+
valign, offset = valign
|
48
|
+
|
49
|
+
if valign == :top
|
50
|
+
@at[1] -= offset
|
51
|
+
return
|
52
|
+
end
|
53
|
+
|
54
|
+
wrap text
|
55
|
+
h = height
|
56
|
+
|
57
|
+
case valign
|
58
|
+
when :center
|
59
|
+
@at[1] -= (@height - h + @descender) * 0.5 + offset
|
60
|
+
when :bottom
|
61
|
+
@at[1] -= (@height - h) + offset
|
62
|
+
end
|
63
|
+
|
64
|
+
@height = h
|
65
|
+
end
|
66
|
+
end)
|
@@ -1,14 +1,19 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
module Fragment
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Prawn::Text::Formatted::Fragment.prepend (Module.new do
|
5
4
|
attr_reader :document
|
6
|
-
|
5
|
+
|
7
6
|
# Prevent fragment from being written by discarding the text.
|
8
7
|
def conceal
|
9
8
|
@text = ''
|
10
9
|
end
|
11
10
|
|
11
|
+
# Don't strip soft hyphens when repacking unretrieved fragments
|
12
|
+
def include_trailing_white_space!
|
13
|
+
@format_state.delete :normalized_soft_hyphen
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
12
17
|
# Modify the built-in ascender write method to allow an override value to be
|
13
18
|
# specified using the format_state hash.
|
14
19
|
def ascender= val
|
@@ -20,18 +25,16 @@ module Fragment
|
|
20
25
|
def descender= val
|
21
26
|
@descender = (format_state.key? :descender) ? format_state[:descender] : val
|
22
27
|
end
|
23
|
-
end
|
24
28
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
def width
|
30
|
+
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
|
32
|
+
else
|
33
|
+
val = super
|
34
|
+
end
|
35
|
+
if (border_offset = format_state[:border_offset])
|
36
|
+
val += border_offset * 2
|
37
|
+
end
|
38
|
+
val
|
33
39
|
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
40
|
+
end)
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Asciidoctor
|
4
|
+
module Prawn
|
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
|
+
# Dispatch to suitable image method in Prawn based on file extension.
|
13
|
+
def image file, opts = {}
|
14
|
+
# FIXME: handle case when SVG is an IO object
|
15
|
+
if ::String === file && (((opts = opts.dup).delete :format) == 'svg' || (file.downcase.end_with? '.svg'))
|
16
|
+
#opts[:enable_file_requests_with_root] = (::File.dirname file) unless opts.key? :enable_file_requests_with_root
|
17
|
+
#opts[:enable_web_requests] = allow_uri_read if !(opts.key? :enable_web_requests) && (respond_to? :allow_uri_read)
|
18
|
+
#opts[:cache_images] = cache_uri if !(opts.key? :cache_images) && (respond_to? :cache_uri)
|
19
|
+
#opts[:fallback_font_name] = fallback_svg_font_name if !(opts.key? :fallback_font_name) && (respond_to? :fallback_svg_font_name)
|
20
|
+
if (opts.key? :fit) && (fit = opts.delete :fit) && !opts[:width] && !opts[:height]
|
21
|
+
svg (::File.read file, mode: 'r:UTF-8'), opts do |svg_doc|
|
22
|
+
max_width, max_height = fit
|
23
|
+
svg_doc.calculate_sizing requested_width: max_width if max_width && svg_doc.sizing.output_width != max_width
|
24
|
+
svg_doc.calculate_sizing requested_height: max_height if max_height && svg_doc.sizing.output_height > max_height
|
25
|
+
end
|
26
|
+
else
|
27
|
+
svg (::File.read file, mode: 'r:UTF-8'), opts
|
28
|
+
end
|
29
|
+
else
|
30
|
+
_initial_image file, opts
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Retrieve the intrinsic image dimensions for the specified path.
|
35
|
+
#
|
36
|
+
# Returns a Hash containing :width and :height keys that map to the image's
|
37
|
+
# intrinsic width and height values (in pixels)
|
38
|
+
def intrinsic_image_dimensions path, format
|
39
|
+
if format == 'svg'
|
40
|
+
img_obj = ::Prawn::SVG::Interface.new (::File.read path, mode: 'r:UTF-8'), self, {}
|
41
|
+
img_size = img_obj.document.sizing
|
42
|
+
{ width: img_size.output_width, height: img_size.output_height }
|
43
|
+
else
|
44
|
+
# NOTE build_image_object caches image data previously loaded
|
45
|
+
_, img_size = ::File.open(path, 'rb') {|fd| build_image_object fd }
|
46
|
+
{ width: img_size.width, height: img_size.height }
|
47
|
+
end
|
48
|
+
rescue
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
::Prawn::Document.extensions << Images
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prawn
|
4
|
+
module SVG
|
5
|
+
class Interface
|
6
|
+
def resize opts = {}
|
7
|
+
sizing = document.sizing
|
8
|
+
sizing.requested_width = opts[:width]
|
9
|
+
sizing.requested_height = opts[:height]
|
10
|
+
sizing.calculate
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end unless Prawn::SVG::Interface.method_defined? :resize
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prawn
|
4
|
+
class Table
|
5
|
+
class Cell
|
6
|
+
class AsciiDoc < Cell
|
7
|
+
attr_accessor :align
|
8
|
+
attr_accessor :valign
|
9
|
+
|
10
|
+
def initialize pdf, opts = {}
|
11
|
+
@font_options = {}
|
12
|
+
super pdf, [], opts
|
13
|
+
end
|
14
|
+
|
15
|
+
def font_style= val
|
16
|
+
@font_options[:style] = val
|
17
|
+
end
|
18
|
+
|
19
|
+
def text_color= val
|
20
|
+
@font_options[:color] = val
|
21
|
+
end
|
22
|
+
|
23
|
+
def size= val
|
24
|
+
@font_options[:size] = val
|
25
|
+
end
|
26
|
+
|
27
|
+
def font= val
|
28
|
+
@font_options[:family] = val
|
29
|
+
end
|
30
|
+
|
31
|
+
# NOTE: automatic image sizing only works if cell has fixed width
|
32
|
+
def dry_run
|
33
|
+
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
|
43
|
+
end
|
44
|
+
end
|
45
|
+
# FIXME: prawn-table doesn't support cell taller than a single page
|
46
|
+
[max_height, height - 0.0001].min
|
47
|
+
end
|
48
|
+
|
49
|
+
def natural_content_width
|
50
|
+
# QUESTION can we get a better estimate of the natural width?
|
51
|
+
@natural_content_width ||= (@pdf.bounds.width - padding_left - padding_right)
|
52
|
+
end
|
53
|
+
|
54
|
+
def natural_content_height
|
55
|
+
# NOTE when natural_content_height is called, we already know max width
|
56
|
+
@natural_content_height ||= dry_run
|
57
|
+
end
|
58
|
+
|
59
|
+
def draw_content
|
60
|
+
pdf = @pdf
|
61
|
+
# NOTE draw_bounded_content automatically adds FPTolerance to width and height
|
62
|
+
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
|
65
|
+
if @valign != :top && (excess_y = spanned_content_height - natural_content_height) > 0
|
66
|
+
pdf.move_down(@valign == :center ? (excess_y.fdiv 2) : excess_y)
|
67
|
+
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
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -1,12 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Prawn::Table::Cell::Text
|
2
4
|
# Override draw_content method to drop cursor advancement
|
3
5
|
remove_method :draw_content
|
4
6
|
def draw_content
|
5
7
|
with_font do
|
6
8
|
with_text_color do
|
7
|
-
(text_box
|
8
|
-
|
9
|
-
|
9
|
+
(text_box \
|
10
|
+
width: spanned_content_width + FPTolerance,
|
11
|
+
height: spanned_content_height + FPTolerance,
|
12
|
+
at: [0, @pdf.cursor]).render
|
10
13
|
end
|
11
14
|
end
|
12
15
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Prawn::Table::Cell
|
4
|
+
remove_method :draw_borders
|
5
|
+
# Draws borders around the cell. Borders are centered on the bounds of
|
6
|
+
# the cell outside of any padding, so the caller is responsible for
|
7
|
+
# setting appropriate padding to ensure the border does not overlap with
|
8
|
+
# cell content.
|
9
|
+
#
|
10
|
+
def draw_borders pt
|
11
|
+
x, y = pt
|
12
|
+
|
13
|
+
@pdf.mask :line_width, :stroke_color do
|
14
|
+
@borders.each do |border|
|
15
|
+
idx = { top: 0, right: 1, bottom: 2, left: 3 }[border]
|
16
|
+
border_color = @border_colors[idx]
|
17
|
+
border_width = @border_widths[idx]
|
18
|
+
border_line = @border_lines[idx]
|
19
|
+
|
20
|
+
next unless border_width > 0
|
21
|
+
|
22
|
+
# Left and right borders are drawn one-half border beyond the center
|
23
|
+
# 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
|
34
|
+
|
35
|
+
case border_line
|
36
|
+
when :dashed
|
37
|
+
@pdf.dash border_width * 4
|
38
|
+
when :dotted
|
39
|
+
@pdf.dash border_width, space: border_width * 2
|
40
|
+
when :solid
|
41
|
+
# normal line style
|
42
|
+
else
|
43
|
+
raise ::ArgumentError, 'border_line must be :solid, :dotted or :dashed'
|
44
|
+
end
|
45
|
+
|
46
|
+
@pdf.line_width = border_width
|
47
|
+
if border_color == 'transparent'
|
48
|
+
@pdf.stroke_color = '000000'
|
49
|
+
@pdf.transparent 0 do
|
50
|
+
@pdf.stroke_line from, to
|
51
|
+
end
|
52
|
+
else
|
53
|
+
@pdf.stroke_color = border_color
|
54
|
+
@pdf.stroke_line from, to
|
55
|
+
end
|
56
|
+
@pdf.undash
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# the following are organized under the Asciidoctor::Prawn namespace
|
4
|
+
require_relative 'prawn/font_metric_cache'
|
5
|
+
require_relative 'prawn/font/afm'
|
6
|
+
require_relative 'prawn/images'
|
7
|
+
require_relative 'prawn/formatted_text/box'
|
8
|
+
require_relative 'prawn/formatted_text/fragment'
|
9
|
+
require_relative 'prawn/extensions'
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pygments.rb'
|
4
|
+
|
5
|
+
module Pygments
|
6
|
+
module Ext
|
7
|
+
module BlockStyles
|
8
|
+
BlockSelectorRx = /^\.highlight *\{([^}]+?)\}/
|
9
|
+
HighlightBackgroundColorRx = /^\.highlight +\.hll +{ *background(?:-color)?: *#([a-fA-F0-9]{6})/
|
10
|
+
HexColorRx = /^#[a-fA-F0-9]{6}$/
|
11
|
+
|
12
|
+
@cache = ::Hash.new do |cache, key|
|
13
|
+
styles = {}
|
14
|
+
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
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
styles[:highlight_background_color] = $1 if HighlightBackgroundColorRx =~ css
|
25
|
+
@cache = cache.merge key => styles
|
26
|
+
styles
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.for style
|
30
|
+
@cache[style]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rouge
|
4
|
+
module Formatters
|
5
|
+
# Transforms a token stream into an array of
|
6
|
+
# formatted text fragments for use with Prawn.
|
7
|
+
class Prawn < Formatter
|
8
|
+
tag 'prawn'
|
9
|
+
|
10
|
+
Tokens = ::Rouge::Token::Tokens
|
11
|
+
LineOrientedTokens = [
|
12
|
+
::Rouge::Token::Tokens::Generic::Inserted,
|
13
|
+
::Rouge::Token::Tokens::Generic::Deleted,
|
14
|
+
::Rouge::Token::Tokens::Generic::Heading,
|
15
|
+
::Rouge::Token::Tokens::Generic::Subheading,
|
16
|
+
]
|
17
|
+
|
18
|
+
LF = ?\n
|
19
|
+
DummyText = ?\u0000
|
20
|
+
NoBreakSpace = ?\u00a0
|
21
|
+
InnerIndent = %(#{LF} )
|
22
|
+
GuardedIndent = NoBreakSpace
|
23
|
+
GuardedInnerIndent = %(#{LF}#{NoBreakSpace})
|
24
|
+
BoldStyle = [:bold].to_set
|
25
|
+
ItalicStyle = [:italic].to_set
|
26
|
+
BoldItalicStyle = [:bold, :italic].to_set
|
27
|
+
UnderlineStyle = [:underline].to_set
|
28
|
+
|
29
|
+
def initialize opts = {}
|
30
|
+
unless ::Rouge::Theme === (theme = opts[:theme])
|
31
|
+
unless theme && (theme = ::Rouge::Theme.find theme)
|
32
|
+
theme = ::Rouge::Themes::AsciidoctorPDFDefault
|
33
|
+
end
|
34
|
+
theme = theme.new
|
35
|
+
end
|
36
|
+
@theme = theme
|
37
|
+
@normalized_colors = {}
|
38
|
+
@background_colorizer = BackgroundColorizer.new line_gap: opts[:line_gap]
|
39
|
+
@linenum_fragment_base = create_fragment Tokens::Generic::Lineno
|
40
|
+
@highlight_line_fragment = create_highlight_line_fragment opts[:highlight_background_color]
|
41
|
+
end
|
42
|
+
|
43
|
+
def background_color
|
44
|
+
@background_color ||= (normalize_color (@theme.style_for Tokens::Text).bg)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Override format method so fragments don't get flatted to a string
|
48
|
+
# and to add an options Hash.
|
49
|
+
def format tokens, opts = {}
|
50
|
+
stream tokens, opts
|
51
|
+
end
|
52
|
+
|
53
|
+
def stream tokens, opts = {}
|
54
|
+
line_numbers = opts[:line_numbers]
|
55
|
+
highlight_lines = opts[:highlight_lines]
|
56
|
+
if line_numbers || highlight_lines
|
57
|
+
linenum = (linenum = opts[:start_line] || 1) > 0 ? linenum : 1
|
58
|
+
fragments = []
|
59
|
+
line_numbers ? (fragments << (create_linenum_fragment linenum)) : (start_of_line = true)
|
60
|
+
fragments << @highlight_line_fragment.dup if highlight_lines && highlight_lines[linenum]
|
61
|
+
tokens.each do |tok, val|
|
62
|
+
if val == LF
|
63
|
+
fragments << { text: LF }
|
64
|
+
linenum += 1
|
65
|
+
line_numbers ? (fragments << (create_linenum_fragment linenum)) : (start_of_line = true)
|
66
|
+
fragments << @highlight_line_fragment.dup if highlight_lines && highlight_lines[linenum]
|
67
|
+
elsif val.include? LF
|
68
|
+
# NOTE we assume if the fragment ends in a line feed, the intention was to match a line-oriented form
|
69
|
+
line_oriented = val.end_with? LF
|
70
|
+
base_fragment = create_fragment tok, val
|
71
|
+
val.each_line do |line|
|
72
|
+
if start_of_line
|
73
|
+
line[0] = GuardedIndent if line.start_with? ' '
|
74
|
+
start_of_line = nil
|
75
|
+
end
|
76
|
+
fragments << (line_oriented ? (base_fragment.merge text: line, inline_block: true) : (base_fragment.merge text: line))
|
77
|
+
next unless line.end_with? LF
|
78
|
+
# NOTE eagerly append linenum fragment or line highlight if there's a next line
|
79
|
+
linenum += 1
|
80
|
+
line_numbers ? (fragments << (create_linenum_fragment linenum)) : (start_of_line = true)
|
81
|
+
fragments << @highlight_line_fragment.dup if highlight_lines && highlight_lines[linenum]
|
82
|
+
end
|
83
|
+
else
|
84
|
+
if start_of_line
|
85
|
+
val[0] = GuardedIndent if val.start_with? ' '
|
86
|
+
start_of_line = nil
|
87
|
+
end
|
88
|
+
fragments << (create_fragment tok, val)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
# NOTE pad numbers that have less digits than the largest line number
|
92
|
+
# FIXME we could store these fragments so we don't have find them again
|
93
|
+
if line_numbers && (linenum_w = linenum.to_s.length) > 1
|
94
|
+
# NOTE extra column is the trailing space after the line number
|
95
|
+
linenum_w += 1
|
96
|
+
fragments.each do |fragment|
|
97
|
+
fragment[:text] = (fragment[:text].rjust linenum_w, NoBreakSpace).to_s if fragment[:linenum]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
fragments
|
101
|
+
else
|
102
|
+
start_of_line = true
|
103
|
+
tokens.map do |tok, val|
|
104
|
+
# match one or more consecutive endlines
|
105
|
+
if val == LF || (val == (LF * val.length))
|
106
|
+
start_of_line = true
|
107
|
+
{ text: val }
|
108
|
+
else
|
109
|
+
val[0] = GuardedIndent if start_of_line && (val.start_with? ' ')
|
110
|
+
val.gsub! InnerIndent, GuardedInnerIndent if val.include? InnerIndent
|
111
|
+
# QUESTION do we need the call to create_fragment if val contains only spaces? consider bg
|
112
|
+
#fragment = create_fragment tok, val
|
113
|
+
fragment = val.rstrip.empty? ? { text: val } : (create_fragment tok, val)
|
114
|
+
# NOTE we assume if the fragment ends in a line feed, the intention was to match a line-oriented form
|
115
|
+
fragment[:inline_block] = true if (start_of_line = val.end_with? LF)
|
116
|
+
fragment
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# TODO: method could still be optimized (for instance, check if val is LF or empty)
|
123
|
+
def create_fragment tok, val = nil
|
124
|
+
fragment = val ? { text: val } : {}
|
125
|
+
if (style_rules = @theme.style_for tok)
|
126
|
+
if (bg = normalize_color style_rules.bg) && bg != background_color
|
127
|
+
fragment[:background_color] = bg
|
128
|
+
fragment[:callback] = @background_colorizer
|
129
|
+
if LineOrientedTokens.include? tok
|
130
|
+
fragment[:inline_block] = true unless style_rules[:inline_block] == false
|
131
|
+
fragment[:extend] = true unless style_rules[:extend] == false
|
132
|
+
else
|
133
|
+
fragment[:inline_block] = true if style_rules[:inline_block]
|
134
|
+
fragment[:extend] = true if style_rules[:extend]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
if (fg = normalize_color style_rules.fg)
|
138
|
+
fragment[:color] = fg
|
139
|
+
end
|
140
|
+
if style_rules[:bold]
|
141
|
+
fragment[:styles] = style_rules[:italic] ? BoldItalicStyle.dup : BoldStyle.dup
|
142
|
+
elsif style_rules[:italic]
|
143
|
+
fragment[:styles] = ItalicStyle.dup
|
144
|
+
end
|
145
|
+
if style_rules[:underline]
|
146
|
+
if fragment.key? :styles
|
147
|
+
fragment[:styles] << UnderlineStyle[0]
|
148
|
+
else
|
149
|
+
fragment[:styles] = UnderlineStyle.dup
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
fragment
|
154
|
+
end
|
155
|
+
|
156
|
+
def create_linenum_fragment linenum
|
157
|
+
@linenum_fragment_base.merge text: %(#{linenum} ), linenum: linenum
|
158
|
+
end
|
159
|
+
|
160
|
+
def create_highlight_line_fragment bg_color
|
161
|
+
{
|
162
|
+
background_color: (bg_color || 'FFFFCC'),
|
163
|
+
callback: @background_colorizer,
|
164
|
+
extend: true,
|
165
|
+
highlight: true,
|
166
|
+
inline_block: true,
|
167
|
+
text: DummyText,
|
168
|
+
width: 0,
|
169
|
+
}
|
170
|
+
end
|
171
|
+
|
172
|
+
def normalize_color raw
|
173
|
+
return unless raw
|
174
|
+
if (normalized = @normalized_colors[raw])
|
175
|
+
normalized
|
176
|
+
else
|
177
|
+
normalized = (raw.start_with? '#') ? (raw.slice 1, raw.length) : raw
|
178
|
+
normalized = normalized.each_char.map {|c| c * 2 }.join if normalized.length == 3
|
179
|
+
@normalized_colors[raw] = normalized
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
class BackgroundColorizer
|
184
|
+
def initialize opts = {}
|
185
|
+
@line_gap = opts[:line_gap] || 0
|
186
|
+
end
|
187
|
+
|
188
|
+
def render_behind fragment
|
189
|
+
pdf = fragment.document
|
190
|
+
data = fragment.format_state
|
191
|
+
prev_fill_color = pdf.fill_color
|
192
|
+
pdf.fill_color data[:background_color]
|
193
|
+
if data[:inline_block]
|
194
|
+
fragment_width = data[:extend] ? pdf.bounds.width - fragment.left : fragment.width
|
195
|
+
v_gap = @line_gap
|
196
|
+
else
|
197
|
+
fragment_width = fragment.width
|
198
|
+
v_gap = 0
|
199
|
+
end
|
200
|
+
pdf.fill_rectangle [fragment.left, fragment.top + v_gap * 0.5], fragment_width, (fragment.height + v_gap)
|
201
|
+
pdf.fill_color prev_fill_color
|
202
|
+
fragment.conceal if fragment.text == DummyText
|
203
|
+
nil
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|