asciidoctor-pdf 1.5.0.beta.8 → 1.5.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +49 -0
  3. data/LICENSE.adoc +1 -1
  4. data/NOTICE.adoc +1 -1
  5. data/README.adoc +43 -47
  6. data/asciidoctor-pdf.gemspec +5 -1
  7. data/bin/asciidoctor-pdf-optimize +1 -1
  8. data/data/themes/base-theme.yml +4 -3
  9. data/data/themes/default-theme.yml +10 -5
  10. data/docs/theming-guide.adoc +286 -22
  11. data/lib/asciidoctor-pdf.rb +1 -0
  12. data/lib/asciidoctor-pdf/converter.rb +1 -0
  13. data/lib/asciidoctor-pdf/version.rb +1 -0
  14. data/lib/asciidoctor/pdf.rb +13 -2
  15. data/lib/asciidoctor/pdf/converter.rb +3962 -3955
  16. data/lib/asciidoctor/pdf/ext.rb +9 -0
  17. data/lib/asciidoctor/pdf/ext/asciidoctor.rb +1 -0
  18. data/lib/asciidoctor/pdf/ext/asciidoctor/abstract_block.rb +1 -0
  19. data/lib/asciidoctor/pdf/ext/asciidoctor/abstract_node.rb +1 -0
  20. data/lib/asciidoctor/pdf/ext/asciidoctor/document.rb +1 -0
  21. data/lib/asciidoctor/pdf/ext/asciidoctor/image.rb +18 -16
  22. data/lib/asciidoctor/pdf/ext/asciidoctor/list.rb +3 -2
  23. data/lib/asciidoctor/pdf/ext/asciidoctor/list_item.rb +2 -1
  24. data/lib/asciidoctor/pdf/ext/asciidoctor/logging_shim.rb +3 -4
  25. data/lib/asciidoctor/pdf/ext/asciidoctor/section.rb +8 -6
  26. data/lib/asciidoctor/pdf/ext/core.rb +2 -0
  27. data/lib/asciidoctor/pdf/ext/core/array.rb +1 -0
  28. data/lib/asciidoctor/pdf/ext/core/hash.rb +1 -0
  29. data/lib/asciidoctor/pdf/ext/core/numeric.rb +4 -3
  30. data/lib/asciidoctor/pdf/ext/core/object.rb +1 -0
  31. data/lib/asciidoctor/pdf/ext/core/quantifiable_stdout.rb +8 -1
  32. data/lib/asciidoctor/pdf/ext/core/regexp.rb +1 -0
  33. data/lib/asciidoctor/pdf/ext/core/string.rb +6 -7
  34. data/lib/asciidoctor/pdf/ext/pdf-core.rb +1 -0
  35. data/lib/asciidoctor/pdf/ext/pdf-core/page.rb +3 -4
  36. data/lib/asciidoctor/pdf/ext/pdf-core/pdf_object.rb +2 -1
  37. data/lib/asciidoctor/pdf/ext/prawn-svg.rb +1 -0
  38. data/lib/asciidoctor/pdf/ext/prawn-svg/interface.rb +11 -8
  39. data/lib/asciidoctor/pdf/ext/prawn-table.rb +2 -1
  40. data/lib/asciidoctor/pdf/ext/prawn-table/cell.rb +9 -10
  41. data/lib/asciidoctor/pdf/ext/prawn-table/cell/asciidoc.rb +62 -57
  42. data/lib/asciidoctor/pdf/ext/prawn-table/cell/text.rb +5 -3
  43. data/lib/asciidoctor/pdf/ext/prawn-templates.rb +1 -0
  44. data/lib/asciidoctor/pdf/ext/prawn.rb +1 -0
  45. data/lib/asciidoctor/pdf/ext/prawn/coderay_encoder.rb +73 -72
  46. data/lib/asciidoctor/pdf/ext/prawn/extensions.rb +814 -818
  47. data/lib/asciidoctor/pdf/ext/prawn/font/afm.rb +4 -3
  48. data/lib/asciidoctor/pdf/ext/prawn/formatted_text/box.rb +2 -1
  49. data/lib/asciidoctor/pdf/ext/prawn/formatted_text/fragment.rb +7 -2
  50. data/lib/asciidoctor/pdf/ext/prawn/images.rb +45 -44
  51. data/lib/asciidoctor/pdf/ext/pygments.rb +34 -0
  52. data/lib/asciidoctor/pdf/ext/rouge.rb +1 -1
  53. data/lib/asciidoctor/pdf/ext/rouge/formatters/prawn.rb +181 -149
  54. data/lib/asciidoctor/pdf/ext/rouge/themes/asciidoctor_pdf_default.rb +1 -0
  55. data/lib/asciidoctor/pdf/formatted_text.rb +2 -0
  56. data/lib/asciidoctor/pdf/formatted_text/formatter.rb +35 -34
  57. data/lib/asciidoctor/pdf/formatted_text/fragment_position_renderer.rb +8 -7
  58. data/lib/asciidoctor/pdf/formatted_text/inline_destination_marker.rb +13 -14
  59. data/lib/asciidoctor/pdf/formatted_text/inline_image_arranger.rb +112 -133
  60. data/lib/asciidoctor/pdf/formatted_text/inline_image_renderer.rb +43 -41
  61. data/lib/asciidoctor/pdf/formatted_text/inline_text_aligner.rb +15 -14
  62. data/lib/asciidoctor/pdf/formatted_text/source_wrap.rb +43 -0
  63. data/lib/asciidoctor/pdf/formatted_text/text_background_and_border_renderer.rb +46 -37
  64. data/lib/asciidoctor/pdf/formatted_text/transform.rb +371 -352
  65. data/lib/asciidoctor/pdf/index_catalog.rb +99 -95
  66. data/lib/asciidoctor/pdf/measurements.rb +51 -48
  67. data/lib/asciidoctor/pdf/optimizer.rb +34 -31
  68. data/lib/asciidoctor/pdf/pdfmark.rb +34 -33
  69. data/lib/asciidoctor/pdf/roman_numeral.rb +80 -79
  70. data/lib/asciidoctor/pdf/sanitizer.rb +38 -37
  71. data/lib/asciidoctor/pdf/temporary_path.rb +10 -9
  72. data/lib/asciidoctor/pdf/text_transformer.rb +101 -100
  73. data/lib/asciidoctor/pdf/theme_loader.rb +258 -256
  74. data/lib/asciidoctor/pdf/version.rb +5 -4
  75. metadata +55 -6
  76. data/lib/asciidoctor/pdf/ext/rouge/themes/bw.rb +0 -39
  77. data/lib/asciidoctor/pdf/ext/ttfunk.rb +0 -9
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Prawn::Font::AFM
3
4
  if defined? ::Asciidoctor::Logging
4
5
  include ::Asciidoctor::Logging
@@ -11,7 +12,7 @@ class Prawn::Font::AFM
11
12
  ?\u202f => ?\u00a0,
12
13
  ?\u2009 => ' ',
13
14
  ?\u25e6 => '-',
14
- ?\u25aa => ?\u00b7
15
+ ?\u25aa => ?\u00b7,
15
16
  }
16
17
 
17
18
  remove_method :normalize_encoding
@@ -29,7 +30,7 @@ class Prawn::Font::AFM
29
30
  text.encode 'windows-1252', undef: :replace, replace: ?\u00ac
30
31
  rescue ::Encoding::InvalidByteSequenceError
31
32
  raise Prawn::Errors::IncompatibleStringEncoding,
32
- %(Your document includes text which is not compatible with the Windows-1252 character set.
33
- If you need full UTF-8 support, use TTF fonts instead of the built-in PDF (AFM) fonts.)
33
+ 'Your document includes text which is not compatible with the Windows-1252 character set.
34
+ If you need full UTF-8 support, use TTF fonts instead of the built-in PDF (AFM) fonts.'
34
35
  end
35
36
  end
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  Prawn::Text::Formatted::Box.prepend (Module.new do
3
4
  def draw_fragment_overlay_styles fragment
4
5
  if (underline = (styles = fragment.styles).include? :underline) || (styles.include? :strikethrough)
5
6
  (doc = fragment.document).save_graphics_state do
6
- if (text_decoration_width = (fs = fragment.format_state)[:text_decoration_width])
7
+ if (text_decoration_width = (fs = fragment.format_state)[:text_decoration_width] || doc.text_decoration_width)
7
8
  doc.line_width = text_decoration_width
8
9
  end
9
10
  if (text_decoration_color = fs[:text_decoration_color])
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  Prawn::Text::Formatted::Fragment.prepend (Module.new do
3
4
  attr_reader :document
4
5
 
@@ -27,9 +28,13 @@ Prawn::Text::Formatted::Fragment.prepend (Module.new do
27
28
 
28
29
  def width
29
30
  if (val = format_state[:width])
30
- (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
31
32
  else
32
- super
33
+ val = super
34
+ end
35
+ if (border_offset = format_state[:border_offset])
36
+ val += border_offset * 2
33
37
  end
38
+ val
34
39
  end
35
40
  end)
@@ -1,53 +1,54 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Asciidoctor
3
- module Prawn
4
- module Images
5
- class << self
6
- def extended base
7
- base.class.__send__ :alias_method, :_initial_image, :image
8
- end
9
- end
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
10
11
 
11
- # Dispatch to suitable image method in Prawn based on file extension.
12
- def image file, opts = {}
13
- # FIXME handle case when SVG is an IO object
14
- if ::String === file && (((opts = opts.dup).delete :format) == 'svg' || (file.downcase.end_with? '.svg'))
15
- #opts[:enable_file_requests_with_root] = (::File.dirname file) unless opts.key? :enable_file_requests_with_root
16
- #opts[:enable_web_requests] = allow_uri_read if !(opts.key? :enable_web_requests) && (respond_to? :allow_uri_read)
17
- #opts[:cache_images] = cache_uri if !(opts.key? :cache_images) && (respond_to? :cache_uri)
18
- #opts[:fallback_font_name] = fallback_svg_font_name if !(opts.key? :fallback_font_name) && (respond_to? :fallback_svg_font_name)
19
- if (opts.key? :fit) && (fit = opts.delete :fit) && !opts[:width] && !opts[:height]
20
- svg (::File.read file, mode: 'r:UTF-8'), opts do |svg_doc|
21
- max_width, max_height = fit
22
- svg_doc.calculate_sizing requested_width: max_width if max_width && svg_doc.sizing.output_width != max_width
23
- svg_doc.calculate_sizing requested_height: max_height if max_height && svg_doc.sizing.output_height > max_height
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
24
31
  end
25
- else
26
- svg (::File.read file, mode: 'r:UTF-8'), opts
27
32
  end
28
- else
29
- _initial_image file, opts
30
- end
31
- end
32
33
 
33
- # Retrieve the intrinsic image dimensions for the specified path.
34
- #
35
- # Returns a Hash containing :width and :height keys that map to the image's
36
- # intrinsic width and height values (in pixels)
37
- def intrinsic_image_dimensions path, format
38
- if format == 'svg'
39
- img_obj = ::Prawn::SVG::Interface.new (::File.read path, mode: 'r:UTF-8'), self, {}
40
- img_size = img_obj.document.sizing
41
- { width: img_size.output_width, height: img_size.output_height }
42
- else
43
- # NOTE build_image_object caches image data previously loaded
44
- _, img_size = ::File.open(path, 'rb') {|fd| build_image_object fd }
45
- { width: img_size.width, height: img_size.height }
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
46
50
  end
47
- rescue
48
- end
49
- end
50
51
 
51
- ::Prawn::Document.extensions << Images
52
- end
52
+ ::Prawn::Document.extensions << Images
53
+ end
53
54
  end
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'rouge'
3
4
  require_relative 'rouge/formatters/prawn'
4
5
  require_relative 'rouge/themes/asciidoctor_pdf_default'
5
- require_relative 'rouge/themes/bw' unless Rouge::Theme.find 'bw'
@@ -1,176 +1,208 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Rouge
3
- module Formatters
4
- # Transforms a token stream into an array of
5
- # formatted text fragments for use with Prawn.
6
- class Prawn < Formatter
7
- tag 'prawn'
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'
8
9
 
9
- Tokens = ::Rouge::Token::Tokens
10
- LineOrientedTokens = [
11
- ::Rouge::Token::Tokens::Generic::Inserted,
12
- ::Rouge::Token::Tokens::Generic::Deleted,
13
- ::Rouge::Token::Tokens::Generic::Heading,
14
- ::Rouge::Token::Tokens::Generic::Subheading
15
- ]
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
+ ]
16
17
 
17
- LF = ?\n
18
- NoBreakSpace = ?\u00a0
19
- InnerIndent = %(#{LF} )
20
- GuardedIndent = NoBreakSpace
21
- GuardedInnerIndent = %(#{LF}#{NoBreakSpace})
22
- BoldStyle = [:bold].to_set
23
- ItalicStyle = [:italic].to_set
24
- BoldItalicStyle = [:bold, :italic].to_set
25
- UnderlineStyle = [:underline].to_set
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
26
28
 
27
- def initialize opts = {}
28
- unless ::Rouge::Theme === (theme = opts[:theme])
29
- unless theme && (theme = ::Rouge::Theme.find theme)
30
- theme = ::Rouge::Themes::AsciidoctorPDFDefault
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]
31
41
  end
32
- theme = theme.new
33
- end
34
- @theme = theme
35
- @normalized_colors = {}
36
- @background_colorizer = BackgroundColorizer.new line_gap: opts[:line_gap]
37
- @linenum_fragment_base = (create_fragment Tokens::Generic::Lineno).merge linenum: true
38
- end
39
42
 
40
- def background_color
41
- @background_color ||= normalize_color((@theme.style_for Tokens::Text).bg)
42
- end
43
-
44
- # Override format method so fragments don't get flatted to a string
45
- # and to add an options Hash.
46
- def format tokens, opts = {}
47
- stream tokens, opts
48
- end
43
+ def background_color
44
+ @background_color ||= (normalize_color (@theme.style_for Tokens::Text).bg)
45
+ end
49
46
 
50
- def stream tokens, opts = {}
51
- if opts[:line_numbers]
52
- if (linenum = opts[:start_line]) > 0
53
- linenum -= 1
54
- else
55
- linenum = 0
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
56
51
  end
57
- fragments = []
58
- fragments << (create_linenum_fragment linenum += 1)
59
- tokens.each do |tok, val|
60
- if val == LF
61
- fragments << { text: LF }
62
- fragments << (create_linenum_fragment linenum += 1)
63
- elsif val.include? LF
64
- # NOTE we assume if the fragment ends in a line feed, the intention was to match a line-oriented form
65
- line_oriented = val.end_with? LF
66
- base_fragment = create_fragment tok, val
67
- val.each_line do |line|
68
- fragments << (line_oriented ? (base_fragment.merge text: line, line_oriented: true) : (base_fragment.merge text: line))
69
- # NOTE append linenum fragment if there's a next line; only works if source doesn't have trailing endline
70
- fragments << (create_linenum_fragment linenum += 1) if line.end_with? LF
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
71
99
  end
100
+ fragments
72
101
  else
73
- fragments << (create_fragment tok, val)
74
- end
75
- end
76
- # NOTE drop orphaned linenum fragment (due to trailing endline in source)
77
- fragments.pop if (last_fragment = fragments[-1]) && last_fragment[:linenum]
78
- # NOTE pad numbers that have less digits than the largest line number
79
- if (linenum_w = linenum.to_s.length) > 1
80
- # NOTE extra column is the trailing space after the line number
81
- linenum_w += 1
82
- fragments.each do |fragment|
83
- fragment[:text] = %(#{fragment[:text].rjust linenum_w, NoBreakSpace}) if fragment[:linenum]
84
- end
85
- end
86
- fragments
87
- else
88
- start_of_line = true
89
- tokens.map do |tok, val|
90
- # match one or more consecutive endlines
91
- if val == LF || (val == (LF * val.length))
92
102
  start_of_line = true
93
- { text: val }
94
- else
95
- val[0] = GuardedIndent if start_of_line && (val.start_with? ' ')
96
- val.gsub! InnerIndent, GuardedInnerIndent if val.include? InnerIndent
97
- # QUESTION do we need the call to create_fragment if val contains only spaces? consider bg
98
- #fragment = create_fragment tok, val
99
- fragment = val.rstrip.empty? ? { text: val } : (create_fragment tok, val)
100
- # NOTE we assume if the fragment ends in a line feed, the intention was to match a line-oriented form
101
- fragment[:line_oriented] = true if (start_of_line = val.end_with? LF)
102
- fragment
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
103
119
  end
104
120
  end
105
- # QUESTION should we strip trailing newline?
106
- end
107
- end
108
121
 
109
- # TODO method could still be optimized (for instance, check if val is LF or empty)
110
- def create_fragment tok, val = nil
111
- fragment = val ? { text: val } : {}
112
- if (style_rules = @theme.style_for tok)
113
- if (bg = normalize_color style_rules.bg) && bg != background_color
114
- fragment[:background_color] = bg
115
- fragment[:callback] = @background_colorizer
116
- if LineOrientedTokens.include? tok
117
- fragment[:inline_block] = true unless style_rules[:inline_block] == false
118
- fragment[:extend] = true unless style_rules[:extend] == false
119
- else
120
- fragment[:inline_block] = true if style_rules[:inline_block]
121
- fragment[:extend] = true if style_rules[:extend]
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
122
152
  end
153
+ fragment
123
154
  end
124
- if (fg = normalize_color style_rules.fg)
125
- fragment[:color] = fg
155
+
156
+ def create_linenum_fragment linenum
157
+ @linenum_fragment_base.merge text: %(#{linenum} ), linenum: linenum
126
158
  end
127
- if style_rules[:bold]
128
- fragment[:styles] = style_rules[:italic] ? BoldItalicStyle.dup : BoldStyle.dup
129
- elsif style_rules[:italic]
130
- fragment[:styles] = ItalicStyle.dup
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
+ }
131
170
  end
132
- if style_rules[:underline]
133
- if fragment.key? :styles
134
- fragment[:styles] << UnderlineStyle[0]
171
+
172
+ def normalize_color raw
173
+ return unless raw
174
+ if (normalized = @normalized_colors[raw])
175
+ normalized
135
176
  else
136
- fragment[:styles] = UnderlineStyle.dup
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
137
180
  end
138
181
  end
139
- end
140
- fragment
141
- end
142
182
 
143
- def create_linenum_fragment linenum
144
- @linenum_fragment_base.merge text: %(#{linenum} )
145
- end
183
+ class BackgroundColorizer
184
+ def initialize opts = {}
185
+ @line_gap = opts[:line_gap] || 0
186
+ end
146
187
 
147
- def normalize_color raw
148
- return unless raw
149
- if (normalized = @normalized_colors[raw])
150
- normalized
151
- else
152
- normalized = (raw.start_with? '#') ? raw[1..-1] : raw
153
- normalized = normalized.each_char.map {|c| c * 2 }.join if normalized.length == 3
154
- @normalized_colors[raw] = normalized
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
155
206
  end
156
207
  end
157
208
  end
158
-
159
- class BackgroundColorizer
160
- def initialize opts = {}
161
- @line_gap = opts[:line_gap] || 0
162
- end
163
-
164
- def render_behind fragment
165
- pdf = fragment.document
166
- data = fragment.format_state
167
- prev_fill_color = pdf.fill_color
168
- pdf.fill_color data[:background_color]
169
- v_gap = data[:inline_block] ? @line_gap : 0
170
- fragment_width = data[:line_oriented] && data[:extend] ? (pdf.bounds.width - fragment.left) : fragment.width
171
- pdf.fill_rectangle [fragment.left, fragment.top + v_gap * 0.5], fragment_width, (fragment.height + v_gap)
172
- pdf.fill_color prev_fill_color
173
- end
174
- end
175
- end
176
- end