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
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ ########################################################################
4
+ #
5
+ # This file was copied from roman-numerals and modified for use with
6
+ # Asciidoctor PDF.
7
+ #
8
+ # Permission is hereby granted, free of charge, to any person obtaining
9
+ # a copy of this software and associated documentation files (the
10
+ # "Software"), to deal in the Software without restriction, including
11
+ # without limitation the rights to use, copy, modify, merge, publish,
12
+ # distribute, sublicense, and/or sell copies of the Software, and to
13
+ # permit persons to whom the Software is furnished to do so, subject to
14
+ # the following conditions:
15
+ #
16
+ # The above copyright notice and this permission notice shall be
17
+ # included in all copies or substantial portions of the Software.
18
+ #
19
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
+ #
27
+ # Copyright (c) 2011 Andrew Vos
28
+ # Copyright (c) 2014 OpenDevise, Inc.
29
+ #
30
+ ########################################################################
31
+
32
+ module Asciidoctor
33
+ module PDF
34
+ class RomanNumeral
35
+ BaseDigits = {
36
+ 1 => 'I',
37
+ 4 => 'IV',
38
+ 5 => 'V',
39
+ 9 => 'IX',
40
+ 10 => 'X',
41
+ 40 => 'XL',
42
+ 50 => 'L',
43
+ 90 => 'XC',
44
+ 100 => 'C',
45
+ 400 => 'CD',
46
+ 500 => 'D',
47
+ 900 => 'CM',
48
+ 1000 => 'M',
49
+ }
50
+
51
+ def initialize initial_value, letter_case = nil
52
+ initial_value ||= 1
53
+ if ::Integer === initial_value
54
+ @integer_value = initial_value
55
+ else
56
+ @integer_value = RomanNumeral.roman_to_int initial_value
57
+ letter_case = :lower if letter_case.nil? && initial_value.upcase != initial_value
58
+ end
59
+ @letter_case = letter_case.nil? ? :upper : letter_case
60
+ end
61
+
62
+ def to_s
63
+ to_r
64
+ end
65
+
66
+ def to_r
67
+ if (int = @integer_value) < 1
68
+ return int.to_s
69
+ end
70
+ roman = RomanNumeral.int_to_roman int
71
+ @letter_case == :lower ? roman.downcase : roman
72
+ end
73
+
74
+ def to_i
75
+ @integer_value
76
+ end
77
+
78
+ def odd?
79
+ to_i.odd?
80
+ end
81
+
82
+ def even?
83
+ to_i.even?
84
+ end
85
+
86
+ def next
87
+ RomanNumeral.new @integer_value + 1, @letter_case
88
+ end
89
+
90
+ def next!
91
+ @integer_value += 1
92
+ self
93
+ end
94
+
95
+ def pred
96
+ RomanNumeral.new @integer_value - 1, @letter_case
97
+ end
98
+
99
+ def empty?
100
+ false
101
+ end
102
+
103
+ def self.int_to_roman value
104
+ result = []
105
+ BaseDigits.keys.reverse_each do |ival|
106
+ while value >= ival
107
+ value -= ival
108
+ result << BaseDigits[ival]
109
+ end
110
+ end
111
+ result.join
112
+ end
113
+
114
+ def self.roman_to_int value
115
+ value = value.upcase
116
+ result = 0
117
+ BaseDigits.values.reverse_each do |rval|
118
+ while value.start_with? rval
119
+ offset = rval.length
120
+ value = value[offset..offset]
121
+ result += BaseDigits.key rval
122
+ end
123
+ end
124
+ result
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asciidoctor
4
+ module PDF
5
+ module Sanitizer
6
+ XMLSpecialChars = {
7
+ '&lt;' => ?<,
8
+ '&gt;' => ?>,
9
+ '&amp;' => ?&,
10
+ }
11
+ XMLSpecialCharsRx = /(?:#{XMLSpecialChars.keys.join ?|})/
12
+ InverseXMLSpecialChars = XMLSpecialChars.invert
13
+ InverseXMLSpecialCharsRx = /[#{InverseXMLSpecialChars.keys.join}]/
14
+ (BuiltInNamedEntities = {
15
+ 'amp' => ?&,
16
+ 'apos' => ?',
17
+ 'gt' => ?>,
18
+ 'lt' => ?<,
19
+ 'nbsp' => ' ',
20
+ 'quot' => ?",
21
+ }).default = ??
22
+ SanitizeXMLRx = /<[^>]+>/
23
+ 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})));/
24
+
25
+ # Strip leading, trailing and repeating whitespace, remove XML tags and
26
+ # resolve all entities in the specified string.
27
+ #
28
+ # FIXME move to a module so we can mix it in elsewhere
29
+ # FIXME add option to control escaping entities, or a filter mechanism in general
30
+ def sanitize string
31
+ string = string.gsub SanitizeXMLRx, '' if string.include? '<'
32
+ string = string.gsub(CharRefRx) { $1 ? BuiltInNamedEntities[$1] : ([$2 ? $2.to_i : ($3.to_i 16)].pack 'U1') } if string.include? '&'
33
+ string.strip.tr_s ' ', ' '
34
+ end
35
+
36
+ def escape_xml string
37
+ string.gsub InverseXMLSpecialCharsRx, InverseXMLSpecialChars
38
+ end
39
+
40
+ def encode_quotes string
41
+ (string.include? ?") ? (string.gsub ?", '&quot;') : string
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ unless RUBY_VERSION >= '2.4'
4
+ begin
5
+ require 'unicode' unless defined? Unicode::VERSION
6
+ rescue LoadError
7
+ begin
8
+ require 'active_support/multibyte' unless defined? ActiveSupport::Multibyte
9
+ rescue LoadError; end
10
+ end
11
+ end
12
+
13
+ module Asciidoctor
14
+ module PDF
15
+ module TextTransformer
16
+ XMLMarkupRx = /&#?[a-z\d]+;|</
17
+ PCDATAFilterRx = /(&#?[a-z\d]+;|<[^>]+>)|([^&<]+)/
18
+ TagFilterRx = /(<[^>]+>)|([^<]+)/
19
+ WordRx = /\S+/
20
+ SoftHyphen = ?\u00ad
21
+
22
+ def capitalize_words_pcdata string
23
+ if XMLMarkupRx.match? string
24
+ string.gsub(PCDATAFilterRx) { $2 ? (capitalize_words_mb $2) : $1 }
25
+ else
26
+ capitalize_words_mb string
27
+ end
28
+ end
29
+
30
+ def capitalize_words_mb string
31
+ string.gsub(WordRx) { capitalize_mb $& }
32
+ end
33
+
34
+ def hyphenate_words_pcdata string, hyphenator
35
+ if XMLMarkupRx.match? string
36
+ string.gsub(PCDATAFilterRx) { $2 ? (hyphenate_words $2, hyphenator) : $1 }
37
+ else
38
+ hyphenate_words string, hyphenator
39
+ end
40
+ end
41
+
42
+ def hyphenate_words string, hyphenator
43
+ string.gsub(WordRx) { hyphenator.visualize $&, SoftHyphen }
44
+ end
45
+
46
+ def lowercase_pcdata string
47
+ if string.include? '<'
48
+ string.gsub(TagFilterRx) { $2 ? (lowercase_mb $2) : $1 }
49
+ else
50
+ lowercase_mb string
51
+ end
52
+ end
53
+
54
+ def uppercase_pcdata string
55
+ if XMLMarkupRx.match? string
56
+ string.gsub(PCDATAFilterRx) { $2 ? (uppercase_mb $2) : $1 }
57
+ else
58
+ uppercase_mb string
59
+ end
60
+ end
61
+
62
+ if RUBY_VERSION >= '2.4'
63
+ def capitalize_mb string
64
+ string.capitalize
65
+ end
66
+
67
+ def lowercase_mb string
68
+ string.downcase
69
+ end
70
+
71
+ def uppercase_mb string
72
+ string.upcase
73
+ end
74
+ # NOTE Unicode library is 4x as fast as ActiveSupport::MultiByte::Chars
75
+ elsif defined? ::Unicode
76
+ def capitalize_mb string
77
+ string.ascii_only? ? string.capitalize : (::Unicode.capitalize string)
78
+ end
79
+
80
+ def lowercase_mb string
81
+ string.ascii_only? ? string.downcase : (::Unicode.downcase string)
82
+ end
83
+
84
+ def uppercase_mb string
85
+ string.ascii_only? ? string.upcase : (::Unicode.upcase string)
86
+ end
87
+ elsif defined? ::ActiveSupport::Multibyte
88
+ MultibyteChars = ::ActiveSupport::Multibyte::Chars
89
+
90
+ def capitalize_mb string
91
+ string.ascii_only? ? string.capitalize : (MultibyteChars.new string).capitalize.to_s
92
+ end
93
+
94
+ def lowercase_mb string
95
+ string.ascii_only? ? string.downcase : (MultibyteChars.new string).downcase.to_s
96
+ end
97
+
98
+ def uppercase_mb string
99
+ string.ascii_only? ? string.upcase : (MultibyteChars.new string).upcase.to_s
100
+ end
101
+ else
102
+ def capitalize_mb string
103
+ string.capitalize
104
+ end
105
+
106
+ def lowercase_mb string
107
+ string.downcase
108
+ end
109
+
110
+ def uppercase_mb string
111
+ string.upcase
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,305 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'safe_yaml/load'
4
+ require 'ostruct'
5
+ require_relative 'measurements'
6
+
7
+ module Asciidoctor
8
+ module PDF
9
+ class ThemeLoader
10
+ include ::Asciidoctor::PDF::Measurements
11
+ include ::Asciidoctor::Logging
12
+
13
+ DataDir = ::File.absolute_path %(#{__dir__}/../../../data)
14
+ ThemesDir = ::File.join DataDir, 'themes'
15
+ FontsDir = ::File.join DataDir, 'fonts'
16
+ BaseThemePath = ::File.join ThemesDir, 'base-theme.yml'
17
+
18
+ VariableRx = /\$([a-z0-9_-]+)/
19
+ LoneVariableRx = /^\$([a-z0-9_-]+)$/
20
+ HexColorEntryRx = /^(?<k> *\p{Graph}+): +(?!null$)(?<q>["']?)(?<h>#)?(?<v>[a-fA-F0-9]{3,6})\k<q> *(?:#.*)?$/
21
+ MultiplyDivideOpRx = /(-?\d+(?:\.\d+)?) +([*\/]) +(-?\d+(?:\.\d+)?)/
22
+ AddSubtractOpRx = /(-?\d+(?:\.\d+)?) +([+\-]) +(-?\d+(?:\.\d+)?)/
23
+ PrecisionFuncRx = /^(round|floor|ceil)\(/
24
+
25
+ # TODO: implement white? & black? methods
26
+ module ColorValue; end
27
+
28
+ class HexColorValue < String
29
+ include ColorValue
30
+ end
31
+
32
+ # A marker module for a normalized CMYK array
33
+ # Prevents normalizing CMYK value more than once
34
+ module CMYKColorValue
35
+ include ColorValue
36
+ def to_s
37
+ %([#{join ', '}])
38
+ end
39
+ end
40
+
41
+ def self.resolve_theme_file theme_name = nil, theme_dir = nil
42
+ # NOTE if .yml extension is given, assume it's a path (don't append -theme.yml)
43
+ if theme_name && (theme_name.end_with? '.yml')
44
+ # FIXME: restrict to jail!
45
+ if theme_dir
46
+ theme_path = ::File.absolute_path theme_name, (theme_dir = ::File.expand_path theme_dir)
47
+ else
48
+ theme_path = ::File.expand_path theme_name
49
+ theme_dir = ::File.dirname theme_path
50
+ end
51
+ else
52
+ theme_dir = theme_dir ? (::File.expand_path theme_dir) : ThemesDir
53
+ theme_path = ::File.absolute_path ::File.join theme_dir, %(#{theme_name || 'default'}-theme.yml)
54
+ end
55
+ [theme_path, theme_dir]
56
+ end
57
+
58
+ def self.resolve_theme_asset asset_path, theme_dir = nil
59
+ ::File.absolute_path asset_path, (theme_dir || ThemesDir)
60
+ end
61
+
62
+ # NOTE base theme is loaded "as is" (no post-processing)
63
+ def self.load_base_theme
64
+ (::OpenStruct.new ::SafeYAML.load_file BaseThemePath).tap {|theme| theme.__dir__ = ThemesDir }
65
+ end
66
+
67
+ def self.load_theme theme_name = nil, theme_dir = nil
68
+ theme_path, theme_dir = resolve_theme_file theme_name, theme_dir
69
+ if theme_path == BaseThemePath
70
+ load_base_theme
71
+ else
72
+ theme_data = load_file theme_path, nil, theme_dir
73
+ unless (::File.dirname theme_path) == ThemesDir
74
+ theme_data.base_align ||= 'left'
75
+ theme_data.base_line_height ||= 1
76
+ theme_data.base_font_color ||= '000000'
77
+ theme_data.code_font_family ||= (theme_data.literal_font_family || 'Courier')
78
+ theme_data.conum_font_family ||= (theme_data.literal_font_family || 'Courier')
79
+ if (heading_font_family = theme_data.heading_font_family)
80
+ theme_data.abstract_title_font_family ||= heading_font_family
81
+ theme_data.sidebar_title_font_family ||= heading_font_family
82
+ end
83
+ end
84
+ theme_data.__dir__ = theme_dir
85
+ theme_data
86
+ end
87
+ end
88
+
89
+ def self.load_file filename, theme_data = nil, theme_dir = nil
90
+ data = ::File.read filename, mode: 'r:UTF-8', newline: :universal
91
+ data = data.each_line.map {|line|
92
+ line.sub(HexColorEntryRx) { %(#{(m = $~)[:k]}: #{m[:h] || (m[:k].end_with? 'color') ? "'#{m[:v]}'" : m[:v]}) }
93
+ }.join unless (::File.dirname filename) == ThemesDir
94
+ yaml_data = ::SafeYAML.load data, filename
95
+ if ::Hash === yaml_data && (yaml_data.key? 'extends')
96
+ if (extends = yaml_data.delete 'extends')
97
+ [*extends].each do |extend_path|
98
+ if extend_path == 'base'
99
+ theme_data = theme_data ? (::OpenStruct.new theme_data.to_h.merge load_base_theme.to_h) : load_base_theme
100
+ next
101
+ elsif extend_path == 'default' || extend_path == 'default-with-fallback-font'
102
+ extend_path, extend_theme_dir = resolve_theme_file extend_path, ThemesDir
103
+ elsif extend_path.start_with? './'
104
+ extend_path, extend_theme_dir = resolve_theme_file extend_path, (::File.dirname filename)
105
+ else
106
+ extend_path, extend_theme_dir = resolve_theme_file extend_path, theme_dir
107
+ end
108
+ theme_data = load_file extend_path, theme_data, extend_theme_dir
109
+ end
110
+ end
111
+ else
112
+ theme_data ||= ((::File.dirname filename) == ThemesDir ? nil : load_base_theme)
113
+ end
114
+ new.load yaml_data, theme_data
115
+ end
116
+
117
+ def load hash, theme_data = nil
118
+ ::Hash === hash ? hash.reduce(theme_data || ::OpenStruct.new) {|data, (key, val)| process_entry key, val, data, true } : (theme_data || ::OpenStruct.new)
119
+ end
120
+
121
+ private
122
+
123
+ def process_entry key, val, data, normalize_key = false
124
+ key = key.tr '-', '_' if normalize_key && (key.include? '-')
125
+ if key == 'font'
126
+ val.each do |subkey, subval|
127
+ process_entry %(#{key}_#{subkey}), subval, data if subkey == 'catalog' || subkey == 'fallbacks'
128
+ end if ::Hash === val
129
+ elsif key == 'font_catalog'
130
+ data[key] = ::Hash === val ? (val.reduce (val.delete 'merge') ? data[key] || {} : {} do |accum, (name, styles)| # rubocop:disable Style/EachWithObject
131
+ styles = %w(normal bold italic bold_italic).map {|style| [style, styles] }.to_h if ::String === styles
132
+ accum[name] = styles.reduce({}) do |subaccum, (style, path)| # rubocop:disable Style/EachWithObject
133
+ if (path.start_with? 'GEM_FONTS_DIR') && (sep = path[13])
134
+ path = %(#{FontsDir}#{sep}#{path.slice 14, path.length})
135
+ end
136
+ subaccum[style == 'regular' ? 'normal' : style] = expand_vars path, data
137
+ subaccum
138
+ end if ::Hash === styles
139
+ accum
140
+ end) : {}
141
+ elsif key == 'font_fallbacks'
142
+ data[key] = ::Array === val ? val.map {|name| expand_vars name.to_s, data } : []
143
+ elsif key.start_with? 'admonition_icon_'
144
+ data[key] = val ? val.map {|(key2, val2)|
145
+ key2 = key2.tr '-', '_' if key2.include? '-'
146
+ [key2.to_sym, (key2.end_with? '_color') ? (to_color evaluate val2, data) : (evaluate val2, data)]
147
+ }.to_h : {}
148
+ elsif ::Hash === val
149
+ val.each do |subkey, subval|
150
+ process_entry %(#{key}_#{key == 'role' || !(subkey.include? '-') ? subkey : (subkey.tr '-', '_')}), subval, data
151
+ end
152
+ elsif key.end_with? '_color'
153
+ # QUESTION do we really need to evaluate_math in this case?
154
+ data[key] = to_color evaluate val, data
155
+ elsif key.end_with? '_content'
156
+ data[key] = (expand_vars val.to_s, data).to_s
157
+ else
158
+ data[key] = evaluate val, data
159
+ end
160
+ data
161
+ end
162
+
163
+ def evaluate expr, vars
164
+ case expr
165
+ when ::String
166
+ evaluate_math expand_vars expr, vars
167
+ when ::Array
168
+ expr.map {|e| evaluate e, vars }
169
+ else
170
+ expr
171
+ end
172
+ end
173
+
174
+ # NOTE we assume expr is a String
175
+ def expand_vars expr, vars
176
+ if (idx = (expr.index '$'))
177
+ if idx == 0 && expr =~ LoneVariableRx
178
+ if (key = $1).include? '-'
179
+ key = key.tr '-', '_'
180
+ end
181
+ if vars.respond_to? key
182
+ vars[key]
183
+ else
184
+ logger.warn %(unknown variable reference in PDF theme: $#{$1})
185
+ expr
186
+ end
187
+ else
188
+ expr.gsub VariableRx do
189
+ if (key = $1).include? '-'
190
+ key = key.tr '-', '_'
191
+ end
192
+ if vars.respond_to? key
193
+ vars[key]
194
+ else
195
+ logger.warn %(unknown variable reference in PDF theme: $#{$1})
196
+ $&
197
+ end
198
+ end
199
+ end
200
+ else
201
+ expr
202
+ end
203
+ end
204
+
205
+ def evaluate_math expr
206
+ return expr if !(::String === expr) || ColorValue === expr
207
+ # resolve measurement values (e.g., 0.5in => 36)
208
+ # QUESTION should we round the value? perhaps leave that to the precision functions
209
+ # NOTE leave % as a string; handled by converter for now
210
+ original, expr = expr, (resolve_measurement_values expr)
211
+ loop do
212
+ if (expr.count '*/') > 0
213
+ result = expr.gsub(MultiplyDivideOpRx) { $1.to_f.send $2.to_sym, $3.to_f }
214
+ unchanged = (result == expr)
215
+ expr = result
216
+ break if unchanged
217
+ else
218
+ break
219
+ end
220
+ end
221
+ loop do
222
+ if (expr.count '+-') > 0
223
+ result = expr.gsub(AddSubtractOpRx) { $1.to_f.send $2.to_sym, $3.to_f }
224
+ unchanged = (result == expr)
225
+ expr = result
226
+ break if unchanged
227
+ else
228
+ break
229
+ end
230
+ end
231
+ if (expr.end_with? ')') && expr =~ PrecisionFuncRx
232
+ op = $1
233
+ offset = op.length + 1
234
+ expr = expr[offset...-1].to_f.send op.to_sym
235
+ end
236
+ if expr == original
237
+ original
238
+ else
239
+ (int_val = expr.to_i) == (flt_val = expr.to_f) ? int_val : flt_val
240
+ end
241
+ end
242
+
243
+ def to_color value
244
+ case value
245
+ when ColorValue
246
+ # already converted
247
+ return value
248
+ when ::Array
249
+ case value.length
250
+ # CMYK value
251
+ when 4
252
+ value = value.map do |e|
253
+ if ::Numeric === e
254
+ e *= 100.0 unless e > 1
255
+ else
256
+ e = e.to_s.chomp('%').to_f
257
+ end
258
+ e == (int_e = e.to_i) ? int_e : e
259
+ end
260
+ case value
261
+ when [0, 0, 0, 0]
262
+ return HexColorValue.new 'FFFFFF'
263
+ when [100, 100, 100, 100]
264
+ return HexColorValue.new '000000'
265
+ else
266
+ value.extend CMYKColorValue
267
+ return value
268
+ end
269
+ # RGB value
270
+ when 3
271
+ return HexColorValue.new value.map {|e| '%02X' % e }.join
272
+ # Nonsense array value; flatten to string
273
+ else
274
+ value = value.join
275
+ end
276
+ when ::String
277
+ if value == 'transparent'
278
+ # FIXME: should we have a TransparentColorValue class?
279
+ return HexColorValue.new value
280
+ elsif value.length == 6
281
+ return HexColorValue.new value.upcase
282
+ end
283
+ when ::NilClass
284
+ return nil
285
+ else
286
+ # Unknown type (usually Integer); coerce to String
287
+ if (value = value.to_s).length == 6
288
+ return HexColorValue.new value.upcase
289
+ end
290
+ end
291
+ case value.length
292
+ when 6
293
+ resolved_value = value
294
+ when 3
295
+ # expand hex shorthand (e.g., f00 -> ff0000)
296
+ resolved_value = value.each_char.map {|c| c * 2 }.join
297
+ else
298
+ # truncate or pad with leading zeros (e.g., ff -> 0000ff)
299
+ resolved_value = (value.slice 0, 6).rjust 6, '0'
300
+ end
301
+ HexColorValue.new resolved_value.upcase
302
+ end
303
+ end
304
+ end
305
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asciidoctor
4
+ module PDF
5
+ VERSION = '1.5.0'
6
+ end
7
+ Pdf = PDF unless const_defined? :Pdf, false
8
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'pdf/version'
4
+ require 'asciidoctor' unless defined? Asciidoctor.load
5
+ require 'prawn'
6
+ begin
7
+ require 'prawn/gmagick'
8
+ rescue LoadError
9
+ end unless defined? GMagick::Image
10
+ require_relative 'pdf/measurements'
11
+ require_relative 'pdf/sanitizer'
12
+ require_relative 'pdf/text_transformer'
13
+ require_relative 'pdf/ext'
14
+ require_relative 'pdf/theme_loader'
15
+ require_relative 'pdf/converter'