asciidoctor-pdf 1.5.0.beta.7 → 1.5.0.beta.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
  # NOTE these are either candidates for inclusion in Asciidoctor core or backports
3
+ require_relative 'asciidoctor/logging_shim' unless defined? Asciidoctor::Logging
4
+ require_relative 'asciidoctor/abstract_node'
3
5
  require_relative 'asciidoctor/abstract_block'
4
6
  require_relative 'asciidoctor/document'
5
7
  require_relative 'asciidoctor/section'
6
8
  require_relative 'asciidoctor/list'
7
9
  require_relative 'asciidoctor/list_item'
8
- require_relative 'asciidoctor/logging_shim' unless defined? Asciidoctor::Logging
9
10
  require_relative 'asciidoctor/image'
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+ class Asciidoctor::AbstractNode
3
+ def remove_attr name
4
+ @attributes.delete name
5
+ end unless method_defined? :remove_attr
6
+ end
@@ -3,4 +3,8 @@ class Array
3
3
  def delete_all *entries
4
4
  entries.map {|entry| delete entry }.compact
5
5
  end unless method_defined? :delete_all
6
+
7
+ def sum
8
+ reduce(&:+)
9
+ end unless method_defined? :sum
6
10
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  class Object
3
3
  # Convert the object to a serialized PDF object.
4
- def to_pdf
4
+ def to_pdf_object
5
5
  ::PDF::Core.pdf_object self
6
6
  end
7
7
  end
@@ -12,16 +12,12 @@ class String
12
12
 
13
13
  # If the string is ASCII only, convert it to a PDF LiteralString object. Otherwise, return self.
14
14
  def as_pdf
15
- if ascii_only?
16
- ::PDF::Core::LiteralString.new(encode ::Encoding::ASCII_8BIT)
17
- else
18
- self
19
- end
15
+ ascii_only? ? (::PDF::Core::LiteralString.new encode ::Encoding::ASCII_8BIT) : self
20
16
  end
21
17
 
22
18
  # Convert the string to a serialized PDF object. If the string can be encoded as ASCII-8BIT, first convert it to a PDF
23
19
  # LiteralString object.
24
- def to_pdf
20
+ def to_pdf_object
25
21
  ::PDF::Core.pdf_object as_pdf
26
22
  end
27
23
  end
@@ -55,9 +55,10 @@ module Prawn; class Table; class Cell
55
55
 
56
56
  def draw_content
57
57
  pdf = @pdf
58
- # NOTE draw_bounded_content adds FPTolerance to width and height, which causes content to overflow
58
+ # NOTE draw_bounded_content automatically adds FPTolerance to width and height
59
59
  pdf.bounds.instance_variable_set :@width, spanned_content_width
60
- pdf.bounds.instance_variable_set :@height, spanned_content_height
60
+ # NOTE we've already reserved the space, so just let the box stretch to the bottom of the page to avoid overflow
61
+ pdf.bounds.instance_variable_set :@height, pdf.y
61
62
  if @valign != :top && (excess_y = spanned_content_height - natural_content_height) > 0
62
63
  pdf.move_down(@valign == :center ? (excess_y.fdiv 2) : excess_y)
63
64
  end
@@ -2,5 +2,6 @@
2
2
  # the following are organized under the Asciidoctor::Prawn namespace
3
3
  require_relative 'prawn/font/afm'
4
4
  require_relative 'prawn/images'
5
+ require_relative 'prawn/formatted_text/box'
5
6
  require_relative 'prawn/formatted_text/fragment'
6
7
  require_relative 'prawn/extensions'
@@ -10,6 +10,7 @@ module Prawn
10
10
  module Extensions
11
11
  include ::Asciidoctor::PDF::Measurements
12
12
  include ::Asciidoctor::PDF::Sanitizer
13
+ include ::Asciidoctor::PDF::TextTransformer
13
14
 
14
15
  FontAwesomeIconSets = %w(fab far fas)
15
16
  IconSets = %w(fab far fas fi pf).to_set
@@ -250,7 +251,7 @@ module Extensions
250
251
  elsif points.end_with? 'em'
251
252
  super(@font_size * points.to_f)
252
253
  elsif points.end_with? '%'
253
- super(@font_size * (points.to_f / 100.0))
254
+ super(@font_size * (points.to_f / 100))
254
255
  else
255
256
  super points.to_f
256
257
  end
@@ -429,12 +430,18 @@ module Extensions
429
430
  when :uppercase, 'uppercase'
430
431
  uppercase_pcdata text
431
432
  when :lowercase, 'lowercase'
432
- lowercase_mb text
433
+ lowercase_pcdata text
434
+ when :capitalize, 'capitalize'
435
+ capitalize_words_pcdata text
433
436
  else
434
437
  text
435
438
  end
436
439
  end
437
440
 
441
+ def hyphenate_text text, hyphenator
442
+ hyphenate_words_pcdata text, hyphenator
443
+ end
444
+
438
445
  # Cursor
439
446
 
440
447
  # Short-circuits the call to the built-in move_up operation
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ Prawn::Text::Formatted::Box.prepend (Module.new do
3
+ def draw_fragment_overlay_styles fragment
4
+ if (underline = (styles = fragment.styles).include? :underline) || (styles.include? :strikethrough)
5
+ (doc = fragment.document).save_graphics_state do
6
+ if (text_decoration_width = (fs = fragment.format_state)[:text_decoration_width])
7
+ doc.line_width = text_decoration_width
8
+ end
9
+ if (text_decoration_color = fs[:text_decoration_color])
10
+ doc.stroke_color = text_decoration_color
11
+ end
12
+ underline ? (doc.stroke_line fragment.underline_points) : (doc.stroke_line fragment.strikethrough_points)
13
+ end
14
+ end
15
+ end
16
+ end)
@@ -7,6 +7,12 @@ Prawn::Text::Formatted::Fragment.prepend (Module.new do
7
7
  @text = ''
8
8
  end
9
9
 
10
+ # Don't strip soft hyphens when repacking unretrieved fragments
11
+ def include_trailing_white_space!
12
+ @format_state.delete :normalized_soft_hyphen
13
+ super
14
+ end
15
+
10
16
  # Modify the built-in ascender write method to allow an override value to be
11
17
  # specified using the format_state hash.
12
18
  def ascender= val
@@ -14,6 +14,7 @@ module Images
14
14
  if ::String === file && (((opts = opts.dup).delete :format) == 'svg' || (file.downcase.end_with? '.svg'))
15
15
  #opts[:enable_file_requests_with_root] = (::File.dirname file) unless opts.key? :enable_file_requests_with_root
16
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)
17
18
  #opts[:fallback_font_name] = fallback_svg_font_name if !(opts.key? :fallback_font_name) && (respond_to? :fallback_svg_font_name)
18
19
  if (opts.key? :fit) && (fit = opts.delete :fit) && !opts[:width] && !opts[:height]
19
20
  svg (::File.read file, mode: 'r:UTF-8'), opts do |svg_doc|
@@ -66,7 +66,8 @@ module InlineImageArranger
66
66
  width: image_w,
67
67
  fallback_font_name: doc.fallback_svg_font_name,
68
68
  enable_web_requests: doc.allow_uri_read,
69
- enable_file_requests_with_root: (::File.dirname image_path)
69
+ enable_file_requests_with_root: (::File.dirname image_path),
70
+ cache_images: doc.cache_uri
70
71
  svg_size = image_w ? svg_obj.document.sizing :
71
72
  # NOTE convert intrinsic dimensions to points; constrain to content width
72
73
  (svg_obj.resize width: [(to_pt svg_obj.document.sizing.output_width, :px), available_w].min)
@@ -137,7 +138,7 @@ module InlineImageArranger
137
138
  fragment[:image_width] = image_w
138
139
  fragment[:image_height] = image_h
139
140
  rescue
140
- logger.warn %(could not embed image: #{image_path}; #{$!.message})
141
+ logger.warn %(could not embed image: #{image_path}; #{$!.message}#{::Prawn::Errors::UnsupportedImageType === $! ? '; install prawn-gmagick gem to add support' : ''})
141
142
  drop = true # delegate to cleanup logic in ensure block
142
143
  ensure
143
144
  # NOTE skip rendering image in scratch document or if image can't be loaded
@@ -24,6 +24,8 @@ class Transform
24
24
  'font_color' => :color,
25
25
  'font_family' => :font,
26
26
  'font_size' => :size,
27
+ 'text_decoration_color' => :text_decoration_color,
28
+ 'text_decoration_width' => :text_decoration_width,
27
29
  }
28
30
  #DummyText = ?\u0000
29
31
 
@@ -73,6 +75,8 @@ class Transform
73
75
  font: theme.link_font_family,
74
76
  size: theme.link_font_size,
75
77
  styles: (to_styles theme.link_font_style, theme.link_text_decoration),
78
+ text_decoration_color: theme.link_text_decoration_color,
79
+ text_decoration_width: theme.link_text_decoration_width,
76
80
  }.compact,
77
81
  mark: {
78
82
  color: theme.mark_font_color,
@@ -98,6 +102,8 @@ class Transform
98
102
  (accum[role] ||= {})[:styles] = to_styles theme[%(role_#{role}_font_style)], theme[%(role_#{role}_text_decoration)]
99
103
  accum
100
104
  end
105
+ @theme_settings['line-through'] = { styles: [:strikethrough].to_set } unless @theme_settings.key? 'line-through'
106
+ @theme_settings['underline'] = { styles: [:underline].to_set } unless @theme_settings.key? 'underline'
101
107
  unless @theme_settings.key? 'big'
102
108
  if (base_font_size_large = theme.base_font_size_large)
103
109
  @theme_settings['big'] = { size: %(#{(base_font_size_large / theme.base_font_size.to_f).round 4}em) }
@@ -119,6 +125,8 @@ class Transform
119
125
  key: { font: 'Courier', styles: [:italic].to_set },
120
126
  link: { color: '0000FF' },
121
127
  mark: { background_color: 'FFFF00', callback: [TextBackgroundAndBorderRenderer] },
128
+ 'line-through' => { styles: [:strikethrough].to_set },
129
+ 'underline' => { styles: [:underline].to_set },
122
130
  'big' => { size: '1.667em' },
123
131
  'small' => { size: '0.8333em' },
124
132
  }
@@ -323,16 +331,11 @@ class Transform
323
331
  end
324
332
  # TODO we could limit to select tags, but doesn't seem to really affect performance
325
333
  attrs[:class].split.each do |class_name|
326
- case class_name
327
- when 'underline'
328
- styles << :underline
329
- when 'line-through'
330
- styles << :strikethrough
331
- else
332
- fragment.update(@theme_settings[class_name]) {|k, oval, nval| k == :styles ? (nval ? oval.merge(nval) : oval.clear) : nval } if @theme_settings.key? class_name
334
+ if @theme_settings.key? class_name
335
+ fragment.update(@theme_settings[class_name]) {|k, oval, nval| k == :styles ? (nval ? oval.merge(nval) : oval.clear) : nval }
333
336
  if fragment[:background_color] || (fragment[:border_color] && fragment[:border_width])
334
337
  fragment[:callback] = ((fragment[:callback] || []) << TextBackgroundAndBorderRenderer).uniq
335
- end
338
+ end
336
339
  end
337
340
  end if attrs.key?(:class)
338
341
  fragment.delete(:styles) if styles.empty?
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  module Asciidoctor; module PDF
3
3
  class IndexCatalog
4
- include Sanitizer
4
+ include ::Asciidoctor::PDF::TextTransformer
5
+
5
6
  LeadingAlphaRx = /^\p{Alpha}/
6
7
 
7
8
  attr_accessor :start_page_number
@@ -1,4 +1,5 @@
1
1
  require 'rghost'
2
+ require 'tmpdir'
2
3
 
3
4
  module Asciidoctor
4
5
  module PDF
@@ -17,14 +18,23 @@ class Optimizer
17
18
  end
18
19
 
19
20
  def generate_file target
20
- filename_o = (filename = Pathname.new target).sub_ext '-o.pdf'
21
- pdfmark = filename.sub_ext '.pdfmark'
22
- (::RGhost::Convert.new target).to :pdf,
23
- filename: filename_o.to_s,
24
- quality: @quality,
25
- d: { Printed: false, CannotEmbedFontPolicy: '/Warning', CompatibilityLevel: @compatibility_level },
26
- raw: pdfmark.file? ? pdfmark.to_s : nil
27
- filename_o.rename target
21
+ ::Dir::Tmpname.create ['asciidoctor-pdf-', '.pdf'] do |tmpfile|
22
+ filename = Pathname.new target
23
+ filename_o = Pathname.new tmpfile
24
+ pdfmark = filename.sub_ext '.pdfmark'
25
+ inputs = pdfmark.file? ? [target, pdfmark.to_s] : target
26
+ (::RGhost::Convert.new inputs).to :pdf,
27
+ filename: filename_o.to_s,
28
+ quality: @quality,
29
+ d: { Printed: false, CannotEmbedFontPolicy: '/Warning', CompatibilityLevel: @compatibility_level }
30
+ begin
31
+ filename_o.rename target
32
+ rescue ::Errno::EXDEV
33
+ filename.binwrite filename_o.binread
34
+ filename_o.unlink
35
+ end
36
+ end
37
+ nil
28
38
  end
29
39
  end
30
40
  end
@@ -11,21 +11,21 @@ class Pdfmark
11
11
  def generate
12
12
  doc = @doc
13
13
  if doc.attr? 'reproducible'
14
- mod_date = creation_date = ::Time.at 0
14
+ mod_date = creation_date = (::Time.at 0).utc
15
15
  else
16
- mod_date = ::Time.parse doc.attr 'docdatetime' rescue (now ||= ::Time.now)
17
- creation_date = ::Time.parse doc.attr 'localdatetime' rescue (now ||= ::Time.now)
16
+ mod_date = (::Time.parse doc.attr 'docdatetime') rescue (now ||= ::Time.now)
17
+ creation_date = (::Time.parse doc.attr 'localdatetime') rescue (now ||= ::Time.now)
18
18
  end
19
19
  # FIXME use sanitize: :plain_text once available
20
20
  content = <<~EOS
21
- [ /Title #{sanitize(doc.doctitle use_fallback: true).to_pdf}
22
- /Author #{(doc.attr 'authors').to_pdf}
23
- /Subject #{(doc.attr 'subject').to_pdf}
24
- /Keywords #{(doc.attr 'keywords').to_pdf}
25
- /ModDate #{mod_date.to_pdf}
26
- /CreationDate #{creation_date.to_pdf}
21
+ [ /Title #{sanitize(doc.doctitle use_fallback: true).to_pdf_object}
22
+ /Author #{(doc.attr 'authors').to_pdf_object}
23
+ /Subject #{(doc.attr 'subject').to_pdf_object}
24
+ /Keywords #{(doc.attr 'keywords').to_pdf_object}
25
+ /ModDate #{mod_date.to_pdf_object}
26
+ /CreationDate #{creation_date.to_pdf_object}
27
27
  /Creator (Asciidoctor PDF #{::Asciidoctor::PDF::VERSION}, based on Prawn #{::Prawn::VERSION})
28
- /Producer #{(doc.attr 'publisher').to_pdf}
28
+ /Producer #{(doc.attr 'publisher').to_pdf_object}
29
29
  /DOCINFO pdfmark
30
30
  EOS
31
31
  content
@@ -1,14 +1,4 @@
1
1
  # frozen_string_literal: true
2
- unless RUBY_VERSION >= '2.4'
3
- begin
4
- require 'unicode' unless defined? Unicode::VERSION
5
- rescue LoadError
6
- begin
7
- require 'active_support/multibyte' unless defined? ActiveSupport::Multibyte
8
- rescue LoadError; end
9
- end
10
- end
11
-
12
2
  module Asciidoctor
13
3
  module PDF
14
4
  module Sanitizer
@@ -29,9 +19,7 @@ module Sanitizer
29
19
  'quot' => ?",
30
20
  }).default = ??
31
21
  SanitizeXMLRx = /<[^>]+>/
32
- XMLMarkupRx = /&#?[a-z\d]+;|</
33
22
  CharRefRx = /&(?:([a-z][a-z]+\d{0,2})|#(?:(\d\d\d{0,4})|x([a-f\d][a-f\d][a-f\d]{0,3})));/
34
- SiftPCDATARx = /(&#?[a-z\d]+;|<[^>]+>)|([^&<]+)/
35
23
 
36
24
  # Strip leading, trailing and repeating whitespace, remove XML tags and
37
25
  # resolve all entities in the specified string.
@@ -51,51 +39,6 @@ module Sanitizer
51
39
  def encode_quotes string
52
40
  (string.include? ?") ? (string.gsub ?", '&quot;') : string
53
41
  end
54
-
55
- def uppercase_pcdata string
56
- if XMLMarkupRx.match? string
57
- string.gsub(SiftPCDATARx) { $2 ? (uppercase_mb $2) : $1 }
58
- else
59
- uppercase_mb string
60
- end
61
- end
62
-
63
- if RUBY_VERSION >= '2.4'
64
- def uppercase_mb string
65
- string.upcase
66
- end
67
-
68
- def lowercase_mb string
69
- string.downcase
70
- end
71
- # NOTE Unicode library is 4x as fast as ActiveSupport::MultiByte::Chars
72
- elsif defined? ::Unicode
73
- def uppercase_mb string
74
- string.ascii_only? ? string.upcase : (::Unicode.upcase string)
75
- end
76
-
77
- def lowercase_mb string
78
- string.ascii_only? ? string.downcase : (::Unicode.downcase string)
79
- end
80
- elsif defined? ::ActiveSupport::Multibyte
81
- MultibyteChars = ::ActiveSupport::Multibyte::Chars
82
-
83
- def uppercase_mb string
84
- string.ascii_only? ? string.upcase : (MultibyteChars.new string).upcase.to_s
85
- end
86
-
87
- def lowercase_mb string
88
- string.ascii_only? ? string.downcase : (MultibyteChars.new string).downcase.to_s
89
- end
90
- else
91
- def uppercase_mb string
92
- string.upcase
93
- end
94
-
95
- def lowercase_mb string
96
- string.downcase
97
- end
98
- end
99
42
  end
100
43
  end
101
44
  end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+ unless RUBY_VERSION >= '2.4'
3
+ begin
4
+ require 'unicode' unless defined? Unicode::VERSION
5
+ rescue LoadError
6
+ begin
7
+ require 'active_support/multibyte' unless defined? ActiveSupport::Multibyte
8
+ rescue LoadError; end
9
+ end
10
+ end
11
+
12
+ module Asciidoctor
13
+ module PDF
14
+ module TextTransformer
15
+ XMLMarkupRx = /&#?[a-z\d]+;|</
16
+ PCDATAFilterRx = /(&#?[a-z\d]+;|<[^>]+>)|([^&<]+)/
17
+ TagFilterRx = /(<[^>]+>)|([^<]+)/
18
+ WordRx = /\S+/
19
+ SoftHyphen = ?\u00ad
20
+
21
+ def capitalize_words_pcdata string
22
+ if XMLMarkupRx.match? string
23
+ string.gsub(PCDATAFilterRx) { $2 ? (capitalize_words_mb $2) : $1 }
24
+ else
25
+ captialize_words_mb string
26
+ end
27
+ end
28
+
29
+ def capitalize_words_mb string
30
+ string.gsub(WordRx) { capitalize_mb $& }
31
+ end
32
+
33
+ def hyphenate_words_pcdata string, hyphenator
34
+ if XMLMarkupRx.match? string
35
+ string.gsub(PCDATAFilterRx) { $2 ? (hyphenate_words $2, hyphenator) : $1 }
36
+ else
37
+ hyphenate_words string, hyphenator
38
+ end
39
+ end
40
+
41
+ def hyphenate_words string, hyphenator
42
+ string.gsub(WordRx) { hyphenator.visualize $&, SoftHyphen }
43
+ end
44
+
45
+ def lowercase_pcdata string
46
+ if string.include? '<'
47
+ string.gsub(TagFilterRx) { $2 ? (lowercase_mb $2) : $1 }
48
+ else
49
+ lowercase_mb string
50
+ end
51
+ end
52
+
53
+ def uppercase_pcdata string
54
+ if XMLMarkupRx.match? string
55
+ string.gsub(PCDATAFilterRx) { $2 ? (uppercase_mb $2) : $1 }
56
+ else
57
+ uppercase_mb string
58
+ end
59
+ end
60
+
61
+ if RUBY_VERSION >= '2.4'
62
+ def capitalize_mb string
63
+ string.capitalize
64
+ end
65
+
66
+ def lowercase_mb string
67
+ string.downcase
68
+ end
69
+
70
+ def uppercase_mb string
71
+ string.upcase
72
+ end
73
+ # NOTE Unicode library is 4x as fast as ActiveSupport::MultiByte::Chars
74
+ elsif defined? ::Unicode
75
+ def capitalize_mb string
76
+ string.ascii_only? ? string.capitalize : (::Unicode.capitalize string)
77
+ end
78
+
79
+ def lowercase_mb string
80
+ string.ascii_only? ? string.downcase : (::Unicode.downcase string)
81
+ end
82
+
83
+ def uppercase_mb string
84
+ string.ascii_only? ? string.upcase : (::Unicode.upcase string)
85
+ end
86
+ elsif defined? ::ActiveSupport::Multibyte
87
+ MultibyteChars = ::ActiveSupport::Multibyte::Chars
88
+
89
+ def capitalize_mb string
90
+ string.ascii_only? ? string.capitalize : (MultibyteChars.new string).capitalize.to_s
91
+ end
92
+
93
+ def lowercase_mb string
94
+ string.ascii_only? ? string.downcase : (MultibyteChars.new string).downcase.to_s
95
+ end
96
+
97
+ def uppercase_mb string
98
+ string.ascii_only? ? string.upcase : (MultibyteChars.new string).upcase.to_s
99
+ end
100
+ else
101
+ def capitalize_mb string
102
+ string.capitalize
103
+ end
104
+
105
+ def lowercase_mb string
106
+ string.downcase
107
+ end
108
+
109
+ def uppercase_mb string
110
+ string.upcase
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end