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.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +32 -0
- data/README.adoc +14 -225
- data/asciidoctor-pdf.gemspec +1 -1
- data/bin/asciidoctor-pdf +1 -6
- data/data/themes/base-theme.yml +2 -0
- data/data/themes/default-theme.yml +4 -0
- data/docs/theming-guide.adoc +136 -19
- data/lib/asciidoctor/pdf/converter.rb +221 -87
- data/lib/asciidoctor/pdf/ext/asciidoctor.rb +2 -1
- data/lib/asciidoctor/pdf/ext/asciidoctor/abstract_node.rb +6 -0
- data/lib/asciidoctor/pdf/ext/core/array.rb +4 -0
- data/lib/asciidoctor/pdf/ext/core/object.rb +1 -1
- data/lib/asciidoctor/pdf/ext/core/string.rb +2 -6
- data/lib/asciidoctor/pdf/ext/prawn-table/cell/asciidoc.rb +3 -2
- data/lib/asciidoctor/pdf/ext/prawn.rb +1 -0
- data/lib/asciidoctor/pdf/ext/prawn/extensions.rb +9 -2
- data/lib/asciidoctor/pdf/ext/prawn/formatted_text/box.rb +16 -0
- data/lib/asciidoctor/pdf/ext/prawn/formatted_text/fragment.rb +6 -0
- data/lib/asciidoctor/pdf/ext/prawn/images.rb +1 -0
- data/lib/asciidoctor/pdf/formatted_text/inline_image_arranger.rb +3 -2
- data/lib/asciidoctor/pdf/formatted_text/transform.rb +11 -8
- data/lib/asciidoctor/pdf/index_catalog.rb +2 -1
- data/lib/asciidoctor/pdf/optimizer.rb +18 -8
- data/lib/asciidoctor/pdf/pdfmark.rb +10 -10
- data/lib/asciidoctor/pdf/sanitizer.rb +0 -57
- data/lib/asciidoctor/pdf/text_transformer.rb +115 -0
- data/lib/asciidoctor/pdf/version.rb +1 -1
- metadata +7 -4
@@ -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'
|
@@ -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
|
-
|
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
|
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
|
58
|
+
# NOTE draw_bounded_content automatically adds FPTolerance to width and height
|
59
59
|
pdf.bounds.instance_variable_set :@width, spanned_content_width
|
60
|
-
|
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
|
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
|
-
|
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
|
-
|
327
|
-
|
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,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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
filename
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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).
|
22
|
-
/Author #{(doc.attr 'authors').
|
23
|
-
/Subject #{(doc.attr 'subject').
|
24
|
-
/Keywords #{(doc.attr 'keywords').
|
25
|
-
/ModDate #{mod_date.
|
26
|
-
/CreationDate #{creation_date.
|
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').
|
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 ?", '"') : 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
|