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.
Files changed (137) hide show
  1. checksums.yaml +5 -5
  2. data/.yardopts +12 -0
  3. data/CHANGELOG.adoc +415 -1
  4. data/LICENSE.adoc +1 -1
  5. data/NOTICE.adoc +14 -11
  6. data/README.adoc +647 -222
  7. data/asciidoctor-pdf.gemspec +47 -44
  8. data/bin/asciidoctor-pdf +5 -9
  9. data/bin/asciidoctor-pdf-optimize +20 -0
  10. data/data/fonts/ABOUT-mplus1mn-subset +26 -0
  11. data/data/fonts/ABOUT-mplus1p-subset +26 -0
  12. data/data/fonts/ABOUT-notoemoji-subset +3 -0
  13. data/data/fonts/ABOUT-notoserif-subset +26 -0
  14. data/data/fonts/{LICENSE-mplus-testflight-58 → LICENSE-mplus} +2 -2
  15. data/data/fonts/{LICENSE-noto-2015-06-05 → LICENSE-notoserif} +0 -0
  16. data/data/fonts/mplus1mn-bold-ascii.ttf +0 -0
  17. data/data/fonts/mplus1mn-bold-subset.ttf +0 -0
  18. data/data/fonts/mplus1mn-bold_italic-ascii.ttf +0 -0
  19. data/data/fonts/mplus1mn-bold_italic-subset.ttf +0 -0
  20. data/data/fonts/mplus1mn-italic-ascii.ttf +0 -0
  21. data/data/fonts/mplus1mn-italic-subset.ttf +0 -0
  22. data/data/fonts/mplus1mn-regular-ascii-conums.ttf +0 -0
  23. data/data/fonts/mplus1mn-regular-subset.ttf +0 -0
  24. data/data/fonts/mplus1p-regular-fallback.ttf +0 -0
  25. data/data/fonts/notoemoji-subset.ttf +0 -0
  26. data/data/fonts/notoserif-bold-subset.ttf +0 -0
  27. data/data/fonts/notoserif-bold_italic-subset.ttf +0 -0
  28. data/data/fonts/notoserif-italic-subset.ttf +0 -0
  29. data/data/fonts/notoserif-regular-subset.ttf +0 -0
  30. data/data/themes/base-theme.yml +26 -4
  31. data/data/themes/default-theme.yml +76 -60
  32. data/data/themes/default-with-fallback-font-theme.yml +9 -0
  33. data/docs/theming-guide.adoc +2731 -922
  34. data/lib/asciidoctor/pdf/converter.rb +4489 -0
  35. data/lib/asciidoctor/pdf/ext/asciidoctor/abstract_block.rb +7 -0
  36. data/lib/asciidoctor/pdf/ext/asciidoctor/abstract_node.rb +7 -0
  37. data/lib/asciidoctor/pdf/ext/asciidoctor/document.rb +5 -0
  38. data/lib/asciidoctor/pdf/ext/asciidoctor/image.rb +35 -0
  39. data/lib/{asciidoctor-pdf/asciidoctor_ext → asciidoctor/pdf/ext/asciidoctor}/list.rb +4 -2
  40. data/lib/{asciidoctor-pdf/asciidoctor_ext → asciidoctor/pdf/ext/asciidoctor}/list_item.rb +3 -1
  41. data/lib/asciidoctor/pdf/ext/asciidoctor/logging_shim.rb +33 -0
  42. data/lib/asciidoctor/pdf/ext/asciidoctor/section.rb +45 -0
  43. data/lib/asciidoctor/pdf/ext/asciidoctor.rb +11 -0
  44. data/lib/{asciidoctor-pdf/core_ext → asciidoctor/pdf/ext/core}/array.rb +6 -6
  45. data/lib/asciidoctor/pdf/ext/core/file.rb +9 -0
  46. data/lib/asciidoctor/pdf/ext/core/hash.rb +7 -0
  47. data/lib/asciidoctor/pdf/ext/core/numeric.rb +26 -0
  48. data/lib/{asciidoctor-pdf/core_ext → asciidoctor/pdf/ext/core}/object.rb +3 -1
  49. data/lib/{asciidoctor-pdf/core_ext → asciidoctor/pdf/ext/core}/quantifiable_stdout.rb +9 -1
  50. data/lib/asciidoctor/pdf/ext/core/regexp.rb +5 -0
  51. data/lib/{asciidoctor-pdf/core_ext → asciidoctor/pdf/ext/core}/string.rb +9 -13
  52. data/lib/asciidoctor/pdf/ext/core.rb +10 -0
  53. data/lib/asciidoctor/pdf/ext/pdf-core/page.rb +54 -0
  54. data/lib/asciidoctor/pdf/ext/pdf-core/pdf_object.rb +8 -0
  55. data/lib/asciidoctor/pdf/ext/pdf-core.rb +4 -0
  56. data/lib/asciidoctor/pdf/ext/prawn/coderay_encoder.rb +117 -0
  57. data/lib/asciidoctor/pdf/ext/prawn/extensions.rb +922 -0
  58. data/lib/{asciidoctor-pdf/prawn_ext → asciidoctor/pdf/ext/prawn}/font/afm.rb +14 -10
  59. data/lib/asciidoctor/pdf/ext/prawn/font_metric_cache.rb +9 -0
  60. data/lib/asciidoctor/pdf/ext/prawn/formatted_text/box.rb +66 -0
  61. data/lib/{asciidoctor-pdf/prawn_ext → asciidoctor/pdf/ext/prawn}/formatted_text/fragment.rb +21 -18
  62. data/lib/asciidoctor/pdf/ext/prawn/images.rb +54 -0
  63. data/lib/asciidoctor/pdf/ext/prawn-svg/interface.rb +14 -0
  64. data/lib/asciidoctor/pdf/ext/prawn-svg.rb +6 -0
  65. data/lib/asciidoctor/pdf/ext/prawn-table/cell/asciidoc.rb +76 -0
  66. data/lib/{asciidoctor-pdf/prawn-table_ext → asciidoctor/pdf/ext/prawn-table}/cell/text.rb +6 -3
  67. data/lib/asciidoctor/pdf/ext/prawn-table/cell.rb +60 -0
  68. data/lib/asciidoctor/pdf/ext/prawn-table.rb +6 -0
  69. data/lib/{asciidoctor-pdf/prawn-templates_ext.rb → asciidoctor/pdf/ext/prawn-templates.rb} +2 -0
  70. data/lib/asciidoctor/pdf/ext/prawn.rb +9 -0
  71. data/lib/asciidoctor/pdf/ext/pygments.rb +34 -0
  72. data/lib/asciidoctor/pdf/ext/rouge/formatters/prawn.rb +208 -0
  73. data/lib/{asciidoctor-pdf/rouge_ext/themes/pastie.rb → asciidoctor/pdf/ext/rouge/themes/asciidoctor_pdf_default.rb} +7 -5
  74. data/lib/asciidoctor/pdf/ext/rouge.rb +5 -0
  75. data/lib/asciidoctor/pdf/ext.rb +9 -0
  76. data/lib/asciidoctor/pdf/formatted_text/formatter.rb +43 -0
  77. data/lib/asciidoctor/pdf/formatted_text/fragment_position_renderer.rb +14 -0
  78. data/lib/asciidoctor/pdf/formatted_text/inline_destination_marker.rb +21 -0
  79. data/lib/asciidoctor/pdf/formatted_text/inline_image_arranger.rb +134 -0
  80. data/lib/asciidoctor/pdf/formatted_text/inline_image_renderer.rb +51 -0
  81. data/lib/asciidoctor/pdf/formatted_text/inline_text_aligner.rb +22 -0
  82. data/lib/{asciidoctor-pdf → asciidoctor/pdf}/formatted_text/parser.rb +175 -53
  83. data/lib/{asciidoctor-pdf → asciidoctor/pdf}/formatted_text/parser.treetop +20 -14
  84. data/lib/asciidoctor/pdf/formatted_text/source_wrap.rb +43 -0
  85. data/lib/asciidoctor/pdf/formatted_text/text_background_and_border_renderer.rb +55 -0
  86. data/lib/asciidoctor/pdf/formatted_text/transform.rb +394 -0
  87. data/lib/{asciidoctor-pdf → asciidoctor/pdf}/formatted_text.rb +6 -0
  88. data/lib/asciidoctor/pdf/index_catalog.rb +133 -0
  89. data/lib/asciidoctor/pdf/measurements.rb +62 -0
  90. data/lib/asciidoctor/pdf/optimizer.rb +44 -0
  91. data/lib/asciidoctor/pdf/pdfmark.rb +41 -0
  92. data/lib/asciidoctor/pdf/roman_numeral.rb +128 -0
  93. data/lib/asciidoctor/pdf/sanitizer.rb +45 -0
  94. data/lib/asciidoctor/pdf/text_transformer.rb +116 -0
  95. data/lib/asciidoctor/pdf/theme_loader.rb +305 -0
  96. data/lib/asciidoctor/pdf/version.rb +8 -0
  97. data/lib/asciidoctor/pdf.rb +15 -0
  98. data/lib/asciidoctor-pdf/converter.rb +2 -3343
  99. data/lib/asciidoctor-pdf/version.rb +3 -5
  100. data/lib/asciidoctor-pdf.rb +3 -3
  101. metadata +242 -128
  102. data/Gemfile +0 -22
  103. data/Rakefile +0 -81
  104. data/lib/asciidoctor-pdf/asciidoctor_ext/image.rb +0 -24
  105. data/lib/asciidoctor-pdf/asciidoctor_ext/section.rb +0 -34
  106. data/lib/asciidoctor-pdf/asciidoctor_ext.rb +0 -5
  107. data/lib/asciidoctor-pdf/core_ext/numeric.rb +0 -15
  108. data/lib/asciidoctor-pdf/core_ext/ostruct.rb +0 -17
  109. data/lib/asciidoctor-pdf/core_ext.rb +0 -4
  110. data/lib/asciidoctor-pdf/formatted_text/formatter.rb +0 -27
  111. data/lib/asciidoctor-pdf/formatted_text/inline_destination_marker.rb +0 -21
  112. data/lib/asciidoctor-pdf/formatted_text/inline_image_arranger.rb +0 -172
  113. data/lib/asciidoctor-pdf/formatted_text/inline_image_renderer.rb +0 -46
  114. data/lib/asciidoctor-pdf/formatted_text/transform.rb +0 -261
  115. data/lib/asciidoctor-pdf/implicit_header_processor.rb +0 -63
  116. data/lib/asciidoctor-pdf/index_catalog.rb +0 -119
  117. data/lib/asciidoctor-pdf/measurements.rb +0 -58
  118. data/lib/asciidoctor-pdf/pdf-core_ext/page.rb +0 -25
  119. data/lib/asciidoctor-pdf/pdf-core_ext/pdf_object.rb +0 -6
  120. data/lib/asciidoctor-pdf/pdf-core_ext.rb +0 -2
  121. data/lib/asciidoctor-pdf/pdfmark.rb +0 -33
  122. data/lib/asciidoctor-pdf/prawn-svg_ext/interface.rb +0 -10
  123. data/lib/asciidoctor-pdf/prawn-svg_ext.rb +0 -4
  124. data/lib/asciidoctor-pdf/prawn-table_ext/cell/asciidoc.rb +0 -69
  125. data/lib/asciidoctor-pdf/prawn-table_ext.rb +0 -3
  126. data/lib/asciidoctor-pdf/prawn_ext/coderay_encoder.rb +0 -115
  127. data/lib/asciidoctor-pdf/prawn_ext/extensions.rb +0 -863
  128. data/lib/asciidoctor-pdf/prawn_ext/images.rb +0 -40
  129. data/lib/asciidoctor-pdf/prawn_ext.rb +0 -5
  130. data/lib/asciidoctor-pdf/roman_numeral.rb +0 -114
  131. data/lib/asciidoctor-pdf/rouge_ext/css_theme.rb +0 -15
  132. data/lib/asciidoctor-pdf/rouge_ext/formatters/prawn.rb +0 -164
  133. data/lib/asciidoctor-pdf/rouge_ext.rb +0 -4
  134. data/lib/asciidoctor-pdf/sanitizer.rb +0 -88
  135. data/lib/asciidoctor-pdf/temporary_path.rb +0 -13
  136. data/lib/asciidoctor-pdf/theme_loader.rb +0 -247
  137. 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,14 +1,19 @@
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.
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
- class ::Prawn::Text::Formatted::Fragment
26
- if respond_to? :prepend
27
- prepend Fragment
28
- else
29
- # NOTE it's necessary to remove the accessor methods or else they won't get replaced
30
- remove_method :ascender=
31
- remove_method :descender=
32
- include Fragment
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,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'prawn-svg' unless defined? Prawn::SVG::Interface
4
+ require_relative 'prawn-svg/interface'
5
+ # NOTE disable system fonts since they're non-portable
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
@@ -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,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