asciidoctor-pdf 1.5.0.beta.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +277 -2
  3. data/LICENSE.adoc +1 -1
  4. data/NOTICE.adoc +1 -1
  5. data/README.adoc +486 -292
  6. data/asciidoctor-pdf.gemspec +12 -11
  7. data/bin/asciidoctor-pdf +2 -6
  8. data/bin/asciidoctor-pdf-optimize +20 -0
  9. data/data/fonts/ABOUT-mplus1mn-subset +26 -0
  10. data/data/fonts/ABOUT-mplus1p-subset +26 -0
  11. data/data/fonts/ABOUT-notoemoji-subset +3 -0
  12. data/data/fonts/ABOUT-notoserif-subset +26 -0
  13. data/data/fonts/{LICENSE-mplus-testflight-58 → LICENSE-mplus} +2 -2
  14. data/data/fonts/{LICENSE-noto-2015-06-05 → LICENSE-notoserif} +0 -0
  15. data/data/fonts/mplus1mn-bold-ascii.ttf +0 -0
  16. data/data/fonts/mplus1mn-bold-subset.ttf +0 -0
  17. data/data/fonts/mplus1mn-bold_italic-ascii.ttf +0 -0
  18. data/data/fonts/mplus1mn-bold_italic-subset.ttf +0 -0
  19. data/data/fonts/mplus1mn-italic-ascii.ttf +0 -0
  20. data/data/fonts/mplus1mn-italic-subset.ttf +0 -0
  21. data/data/fonts/mplus1mn-regular-ascii-conums.ttf +0 -0
  22. data/data/fonts/mplus1mn-regular-subset.ttf +0 -0
  23. data/data/fonts/mplus1p-regular-fallback.ttf +0 -0
  24. data/data/fonts/notoemoji-subset.ttf +0 -0
  25. data/data/fonts/notoserif-bold-subset.ttf +0 -0
  26. data/data/fonts/notoserif-bold_italic-subset.ttf +0 -0
  27. data/data/fonts/notoserif-italic-subset.ttf +0 -0
  28. data/data/fonts/notoserif-regular-subset.ttf +0 -0
  29. data/data/themes/base-theme.yml +22 -4
  30. data/data/themes/default-theme.yml +59 -29
  31. data/data/themes/default-with-fallback-font-theme.yml +4 -17
  32. data/docs/theming-guide.adoc +1647 -167
  33. data/lib/asciidoctor/pdf/converter.rb +4489 -0
  34. data/lib/{asciidoctor-pdf/asciidoctor_ext → asciidoctor/pdf/ext/asciidoctor}/abstract_block.rb +2 -0
  35. data/lib/asciidoctor/pdf/ext/asciidoctor/abstract_node.rb +7 -0
  36. data/lib/{asciidoctor-pdf/asciidoctor_ext → asciidoctor/pdf/ext/asciidoctor}/document.rb +2 -0
  37. data/lib/asciidoctor/pdf/ext/asciidoctor/image.rb +35 -0
  38. data/lib/{asciidoctor-pdf/asciidoctor_ext → asciidoctor/pdf/ext/asciidoctor}/list.rb +4 -2
  39. data/lib/{asciidoctor-pdf/asciidoctor_ext → asciidoctor/pdf/ext/asciidoctor}/list_item.rb +3 -1
  40. data/lib/asciidoctor/pdf/ext/asciidoctor/logging_shim.rb +33 -0
  41. data/lib/{asciidoctor-pdf/asciidoctor_ext → asciidoctor/pdf/ext/asciidoctor}/section.rb +9 -6
  42. data/lib/asciidoctor/pdf/ext/asciidoctor.rb +11 -0
  43. data/lib/{asciidoctor-pdf/core_ext → asciidoctor/pdf/ext/core}/array.rb +6 -0
  44. data/lib/asciidoctor/pdf/ext/core/file.rb +9 -0
  45. data/lib/{asciidoctor-pdf/core_ext → asciidoctor/pdf/ext/core}/hash.rb +2 -0
  46. data/lib/{asciidoctor-pdf/core_ext → asciidoctor/pdf/ext/core}/numeric.rb +5 -3
  47. data/lib/{asciidoctor-pdf/core_ext → asciidoctor/pdf/ext/core}/object.rb +3 -1
  48. data/lib/{asciidoctor-pdf/core_ext → asciidoctor/pdf/ext/core}/quantifiable_stdout.rb +9 -1
  49. data/lib/{asciidoctor-pdf/core_ext → asciidoctor/pdf/ext/core}/regexp.rb +2 -0
  50. data/lib/{asciidoctor-pdf/core_ext → asciidoctor/pdf/ext/core}/string.rb +9 -13
  51. data/lib/asciidoctor/pdf/ext/core.rb +10 -0
  52. data/lib/asciidoctor/pdf/ext/pdf-core/page.rb +54 -0
  53. data/lib/asciidoctor/pdf/ext/pdf-core/pdf_object.rb +8 -0
  54. data/lib/asciidoctor/pdf/ext/pdf-core.rb +4 -0
  55. data/lib/asciidoctor/pdf/ext/prawn/coderay_encoder.rb +117 -0
  56. data/lib/asciidoctor/pdf/ext/prawn/extensions.rb +922 -0
  57. data/lib/{asciidoctor-pdf/prawn_ext → asciidoctor/pdf/ext/prawn}/font/afm.rb +14 -10
  58. data/lib/asciidoctor/pdf/ext/prawn/font_metric_cache.rb +9 -0
  59. data/lib/asciidoctor/pdf/ext/prawn/formatted_text/box.rb +66 -0
  60. data/lib/{asciidoctor-pdf/prawn_ext → asciidoctor/pdf/ext/prawn}/formatted_text/fragment.rb +16 -12
  61. data/lib/asciidoctor/pdf/ext/prawn/images.rb +54 -0
  62. data/lib/asciidoctor/pdf/ext/prawn-svg/interface.rb +14 -0
  63. data/lib/{asciidoctor-pdf/prawn-svg_ext.rb → asciidoctor/pdf/ext/prawn-svg.rb} +3 -1
  64. data/lib/asciidoctor/pdf/ext/prawn-table/cell/asciidoc.rb +76 -0
  65. data/lib/{asciidoctor-pdf/prawn-table_ext → asciidoctor/pdf/ext/prawn-table}/cell/text.rb +6 -3
  66. data/lib/{asciidoctor-pdf/prawn-table_ext → asciidoctor/pdf/ext/prawn-table}/cell.rb +10 -10
  67. data/lib/asciidoctor/pdf/ext/prawn-table.rb +6 -0
  68. data/lib/{asciidoctor-pdf/prawn-templates_ext.rb → asciidoctor/pdf/ext/prawn-templates.rb} +2 -0
  69. data/lib/asciidoctor/pdf/ext/prawn.rb +9 -0
  70. data/lib/asciidoctor/pdf/ext/pygments.rb +34 -0
  71. data/lib/asciidoctor/pdf/ext/rouge/formatters/prawn.rb +208 -0
  72. data/lib/{asciidoctor-pdf/rouge_ext → asciidoctor/pdf/ext/rouge}/themes/asciidoctor_pdf_default.rb +2 -0
  73. data/lib/asciidoctor/pdf/ext/rouge.rb +5 -0
  74. data/lib/asciidoctor/pdf/ext.rb +9 -0
  75. data/lib/asciidoctor/pdf/formatted_text/formatter.rb +43 -0
  76. data/lib/asciidoctor/pdf/formatted_text/fragment_position_renderer.rb +14 -0
  77. data/lib/asciidoctor/pdf/formatted_text/inline_destination_marker.rb +21 -0
  78. data/lib/asciidoctor/pdf/formatted_text/inline_image_arranger.rb +134 -0
  79. data/lib/asciidoctor/pdf/formatted_text/inline_image_renderer.rb +51 -0
  80. data/lib/asciidoctor/pdf/formatted_text/inline_text_aligner.rb +22 -0
  81. data/lib/{asciidoctor-pdf → asciidoctor/pdf}/formatted_text/parser.rb +31 -7
  82. data/lib/{asciidoctor-pdf → asciidoctor/pdf}/formatted_text/parser.treetop +3 -4
  83. data/lib/asciidoctor/pdf/formatted_text/source_wrap.rb +43 -0
  84. data/lib/asciidoctor/pdf/formatted_text/text_background_and_border_renderer.rb +55 -0
  85. data/lib/asciidoctor/pdf/formatted_text/transform.rb +394 -0
  86. data/lib/{asciidoctor-pdf → asciidoctor/pdf}/formatted_text.rb +4 -0
  87. data/lib/asciidoctor/pdf/index_catalog.rb +133 -0
  88. data/lib/asciidoctor/pdf/measurements.rb +62 -0
  89. data/lib/asciidoctor/pdf/optimizer.rb +44 -0
  90. data/lib/asciidoctor/pdf/pdfmark.rb +41 -0
  91. data/lib/asciidoctor/pdf/roman_numeral.rb +128 -0
  92. data/lib/asciidoctor/pdf/sanitizer.rb +45 -0
  93. data/lib/asciidoctor/pdf/text_transformer.rb +116 -0
  94. data/lib/asciidoctor/pdf/theme_loader.rb +305 -0
  95. data/lib/asciidoctor/pdf/version.rb +8 -1
  96. data/lib/asciidoctor/pdf.rb +15 -1
  97. data/lib/asciidoctor-pdf/converter.rb +2 -3824
  98. data/lib/asciidoctor-pdf/version.rb +3 -6
  99. data/lib/asciidoctor-pdf.rb +3 -4
  100. metadata +130 -85
  101. data/lib/asciidoctor-pdf/asciidoctor_ext/image.rb +0 -24
  102. data/lib/asciidoctor-pdf/asciidoctor_ext/logging_shim.rb +0 -25
  103. data/lib/asciidoctor-pdf/asciidoctor_ext.rb +0 -8
  104. data/lib/asciidoctor-pdf/core_ext/ostruct.rb +0 -8
  105. data/lib/asciidoctor-pdf/core_ext.rb +0 -6
  106. data/lib/asciidoctor-pdf/formatted_text/formatter.rb +0 -40
  107. data/lib/asciidoctor-pdf/formatted_text/inline_destination_marker.rb +0 -21
  108. data/lib/asciidoctor-pdf/formatted_text/inline_image_arranger.rb +0 -160
  109. data/lib/asciidoctor-pdf/formatted_text/inline_image_renderer.rb +0 -46
  110. data/lib/asciidoctor-pdf/formatted_text/inline_text_aligner.rb +0 -20
  111. data/lib/asciidoctor-pdf/formatted_text/text_background_and_border_renderer.rb +0 -45
  112. data/lib/asciidoctor-pdf/formatted_text/transform.rb +0 -294
  113. data/lib/asciidoctor-pdf/implicit_header_processor.rb +0 -63
  114. data/lib/asciidoctor-pdf/index_catalog.rb +0 -127
  115. data/lib/asciidoctor-pdf/measurements.rb +0 -58
  116. data/lib/asciidoctor-pdf/pdf-core_ext/page.rb +0 -25
  117. data/lib/asciidoctor-pdf/pdf-core_ext/pdf_object.rb +0 -6
  118. data/lib/asciidoctor-pdf/pdf-core_ext.rb +0 -2
  119. data/lib/asciidoctor-pdf/pdfmark.rb +0 -33
  120. data/lib/asciidoctor-pdf/prawn-svg_ext/interface.rb +0 -10
  121. data/lib/asciidoctor-pdf/prawn-table_ext/cell/asciidoc.rb +0 -69
  122. data/lib/asciidoctor-pdf/prawn-table_ext.rb +0 -4
  123. data/lib/asciidoctor-pdf/prawn_ext/coderay_encoder.rb +0 -115
  124. data/lib/asciidoctor-pdf/prawn_ext/extensions.rb +0 -904
  125. data/lib/asciidoctor-pdf/prawn_ext/images.rb +0 -51
  126. data/lib/asciidoctor-pdf/prawn_ext.rb +0 -5
  127. data/lib/asciidoctor-pdf/roman_numeral.rb +0 -126
  128. data/lib/asciidoctor-pdf/rouge_ext/formatters/prawn.rb +0 -175
  129. data/lib/asciidoctor-pdf/rouge_ext/themes/bw.rb +0 -38
  130. data/lib/asciidoctor-pdf/rouge_ext.rb +0 -4
  131. data/lib/asciidoctor-pdf/sanitizer.rb +0 -101
  132. data/lib/asciidoctor-pdf/temporary_path.rb +0 -13
  133. data/lib/asciidoctor-pdf/theme_loader.rb +0 -280
  134. 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
- %(\u200b) => '',
4
- %(\u202f) => %(\u00a0),
5
- %(\u2009) => ' ',
6
- %(\u25e6) => '-',
7
- %(\u25aa) => %(\u00b7)
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 'The following text could not be fully converted to the Windows-1252 character set:'
21
- warn %(#{text.gsub(/^/, '| ').rstrip})
22
- text.encode 'windows-1252', undef: :replace, replace: %(\u00ac)
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
- %(Your document includes text which is not compatible with the Windows-1252 character set.
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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Prawn::FontMetricCache::CacheEntry
4
+ # workaround for https://github.com/prawnpdf/prawn/issues/1140
5
+ def initialize font, options, size
6
+ font = font.hash
7
+ super
8
+ end
9
+ 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,7 +1,6 @@
1
- module Asciidoctor
2
- module Prawn
3
- module FormattedText
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.
@@ -9,6 +8,12 @@ module Fragment
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
@@ -23,14 +28,13 @@ module Fragment
23
28
 
24
29
  def width
25
30
  if (val = format_state[:width])
26
- (val.end_with? 'em') ? val.to_f * @document.font_size : val
31
+ val = (val.end_with? 'em') ? val.to_f * @document.font_size : (@document.str_to_pt val) if ::String === val
27
32
  else
28
- super
33
+ val = super
29
34
  end
35
+ if (border_offset = format_state[:border_offset])
36
+ val += border_offset * 2
37
+ end
38
+ val
30
39
  end
31
- end
32
-
33
- ::Prawn::Text::Formatted::Fragment.prepend Fragment
34
- end
35
- end
36
- 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
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'prawn-svg' unless defined? Prawn::SVG::Interface
2
- require_relative 'prawn-svg_ext/interface'
4
+ require_relative 'prawn-svg/interface'
3
5
  # NOTE disable system fonts since they're non-portable
4
6
  Prawn::SVG::Interface.font_path.clear
@@ -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 width: spanned_content_width + FPTolerance,
8
- height: spanned_content_height + FPTolerance,
9
- at: [0, @pdf.cursor]).render
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Prawn::Table::Cell
2
4
  remove_method :draw_borders
3
5
  # Draws borders around the cell. Borders are centered on the bounds of
@@ -5,10 +7,10 @@ class Prawn::Table::Cell
5
7
  # setting appropriate padding to ensure the border does not overlap with
6
8
  # cell content.
7
9
  #
8
- def draw_borders(pt)
10
+ def draw_borders pt
9
11
  x, y = pt
10
12
 
11
- @pdf.mask(:line_width, :stroke_color) do
13
+ @pdf.mask :line_width, :stroke_color do
12
14
  @borders.each do |border|
13
15
  idx = { top: 0, right: 1, bottom: 2, left: 3 }[border]
14
16
  border_color = @border_colors[idx]
@@ -21,15 +23,13 @@ class Prawn::Table::Cell
21
23
  # of the corner, so that the corners end up square.
22
24
  from, to = case border
23
25
  when :top
24
- [[x, y], [x+width, y]]
26
+ [[x, y], [x + width, y]]
25
27
  when :bottom
26
- [[x, y-height], [x+width, y-height]]
28
+ [[x, y - height], [x + width, y - height]]
27
29
  when :left
28
- [[x, y + (border_top_width / 2.0)],
29
- [x, y - height - (border_bottom_width / 2.0)]]
30
+ [[x, y + (border_top_width / 2.0)], [x, y - height - (border_bottom_width / 2.0)]]
30
31
  when :right
31
- [[x+width, y + (border_top_width / 2.0)],
32
- [x+width, y - height - (border_bottom_width / 2.0)]]
32
+ [[x + width, y + (border_top_width / 2.0)], [x + width, y - height - (border_bottom_width / 2.0)]]
33
33
  end
34
34
 
35
35
  case border_line
@@ -47,11 +47,11 @@ class Prawn::Table::Cell
47
47
  if border_color == 'transparent'
48
48
  @pdf.stroke_color = '000000'
49
49
  @pdf.transparent 0 do
50
- @pdf.stroke_line(from, to)
50
+ @pdf.stroke_line from, to
51
51
  end
52
52
  else
53
53
  @pdf.stroke_color = border_color
54
- @pdf.stroke_line(from, to)
54
+ @pdf.stroke_line from, to
55
55
  end
56
56
  @pdf.undash
57
57
  end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require ENV['PRAWN_TABLE_REQUIRE_PATH'] || 'prawn/table' unless defined? Prawn::Table::VERSION
4
+ require_relative 'prawn-table/cell'
5
+ require_relative 'prawn-table/cell/asciidoc'
6
+ require_relative 'prawn-table/cell/text'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Prawn::Document
2
4
  # NOTE allows prawn-templates 0.0.4 to be used with prawn >= 2.2.0
3
5
  const_set :VALID_OPTIONS, (send :remove_const, :VALID_OPTIONS).dup if VALID_OPTIONS.frozen?
@@ -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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rouge
2
4
  module Themes
3
5
  # A variation on the pastie style from Pygments, customized for Asciidoctor PDF
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rouge'
4
+ require_relative 'rouge/formatters/prawn'
5
+ require_relative 'rouge/themes/asciidoctor_pdf_default'