asciidoctor-pdf 1.5.0.alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.adoc +22 -0
  3. data/NOTICE.adoc +76 -0
  4. data/README.adoc +263 -0
  5. data/Rakefile +78 -0
  6. data/bin/asciidoctor-pdf +15 -0
  7. data/bin/optimize-pdf +63 -0
  8. data/data/fonts/LICENSE-liberation-fonts-2.00.1 +102 -0
  9. data/data/fonts/LICENSE-mplus-testflight-58 +16 -0
  10. data/data/fonts/LICENSE-noto-fonts-2014-01-30 +201 -0
  11. data/data/fonts/liberationmono-bold-latin.ttf +0 -0
  12. data/data/fonts/liberationmono-bolditalic-latin.ttf +0 -0
  13. data/data/fonts/liberationmono-italic-latin.ttf +0 -0
  14. data/data/fonts/liberationmono-regular-latin.ttf +0 -0
  15. data/data/fonts/mplus1mn-bold-ascii.ttf +0 -0
  16. data/data/fonts/mplus1mn-bolditalic-ascii.ttf +0 -0
  17. data/data/fonts/mplus1mn-italic-ascii.ttf +0 -0
  18. data/data/fonts/mplus1mn-regular-ascii-conums.ttf +0 -0
  19. data/data/fonts/mplus1p-bold-latin.ttf +0 -0
  20. data/data/fonts/mplus1p-light-latin.ttf +0 -0
  21. data/data/fonts/mplus1p-regular-latin.ttf +0 -0
  22. data/data/fonts/mplus1p-regular-multilingual.ttf +0 -0
  23. data/data/fonts/notoserif-bold-latin.ttf +0 -0
  24. data/data/fonts/notoserif-bolditalic-latin.ttf +0 -0
  25. data/data/fonts/notoserif-italic-latin.ttf +0 -0
  26. data/data/fonts/notoserif-regular-latin.ttf +0 -0
  27. data/data/themes/asciidoctor-theme.yml +174 -0
  28. data/data/themes/default-theme.yml +182 -0
  29. data/examples/chronicles.adoc +429 -0
  30. data/examples/chronicles.pdf +0 -0
  31. data/examples/example-pdf-screenshot.png +0 -0
  32. data/examples/example.adoc +27 -0
  33. data/examples/example.pdf +0 -0
  34. data/examples/sample-title-logo.jpg +0 -0
  35. data/examples/wolpertinger.jpg +0 -0
  36. data/lib/asciidoctor-pdf.rb +3 -0
  37. data/lib/asciidoctor-pdf/asciidoctor_ext.rb +1 -0
  38. data/lib/asciidoctor-pdf/asciidoctor_ext/section.rb +26 -0
  39. data/lib/asciidoctor-pdf/converter.rb +1365 -0
  40. data/lib/asciidoctor-pdf/core_ext/array.rb +5 -0
  41. data/lib/asciidoctor-pdf/core_ext/ostruct.rb +9 -0
  42. data/lib/asciidoctor-pdf/implicit_header_processor.rb +59 -0
  43. data/lib/asciidoctor-pdf/pdfmarks.rb +30 -0
  44. data/lib/asciidoctor-pdf/prawn_ext.rb +3 -0
  45. data/lib/asciidoctor-pdf/prawn_ext/coderay_encoder.rb +94 -0
  46. data/lib/asciidoctor-pdf/prawn_ext/extensions.rb +529 -0
  47. data/lib/asciidoctor-pdf/prawn_ext/formatted_text/formatter.rb +29 -0
  48. data/lib/asciidoctor-pdf/prawn_ext/formatted_text/parser.rb +1012 -0
  49. data/lib/asciidoctor-pdf/prawn_ext/formatted_text/parser.treetop +115 -0
  50. data/lib/asciidoctor-pdf/prawn_ext/formatted_text/transform.rb +178 -0
  51. data/lib/asciidoctor-pdf/roman_numeral.rb +107 -0
  52. data/lib/asciidoctor-pdf/theme_loader.rb +103 -0
  53. data/lib/asciidoctor-pdf/version.rb +5 -0
  54. metadata +248 -0
@@ -0,0 +1,115 @@
1
+ module Asciidoctor
2
+ module Prawn
3
+ grammar FormattedText
4
+ rule text
5
+ complex
6
+ end
7
+
8
+ rule complex
9
+ (cdata / element / entity)* {
10
+ def content
11
+ elements.map {|e| e.content }
12
+ end
13
+ }
14
+ end
15
+
16
+ rule element
17
+ # strict tag matching (costs a minor toll)
18
+ # empty_element / start_tag complex end_tag &{|seq| seq[0].name == seq[2].name } {
19
+
20
+ empty_element / start_tag complex end_tag {
21
+ # NOTE content only applies to non-empty element
22
+ def content
23
+ { type: :element, name: (tag_element = elements[0]).name.to_sym, attributes: tag_element.attributes, pcdata: elements[1].content }
24
+ end
25
+ }
26
+ end
27
+
28
+ rule empty_element
29
+ '<br' (spaces? '/')? '>' {
30
+ def content
31
+ { type: :element, name: :br, attributes: {} }
32
+ end
33
+ }
34
+ end
35
+
36
+ rule start_tag
37
+ '<' tag_name attributes '>' {
38
+ def name
39
+ elements[1].text_value
40
+ end
41
+
42
+ def attributes
43
+ elements[2].content
44
+ end
45
+ }
46
+ end
47
+
48
+ rule tag_name
49
+ # QUESTION faster to do regex?
50
+ 'a' / 'b' / 'code' / 'color' / 'del' / 'em' / 'font' / 'i' / 'link' / 'span' / 'strikethrough' / 'strong' / 'sub' / 'sup' / 'u'
51
+ end
52
+
53
+ rule attributes
54
+ attribute* {
55
+ def content
56
+ attrs = {}
57
+ elements.each {|e|
58
+ attr_name, attr_val = e.content
59
+ attrs[attr_name.to_sym] = attr_val
60
+ }
61
+ attrs
62
+ end
63
+ }
64
+ end
65
+
66
+ rule attribute
67
+ spaces [a-z_]+ '=' '"' [^"]* '"' {
68
+ def content
69
+ [elements[1].text_value, elements[4].text_value]
70
+ end
71
+ }
72
+ end
73
+
74
+ rule end_tag
75
+ '</' tag_name '>' {
76
+ def name
77
+ elements[1].text_value
78
+ end
79
+ }
80
+ end
81
+
82
+ rule cdata
83
+ [^<&]+ {
84
+ def content
85
+ { type: :text, value: text_value }
86
+ end
87
+ }
88
+ end
89
+
90
+ rule entity
91
+ '&' ('#' entity_number / entity_name) ';' {
92
+ def content
93
+ if (entity_value = elements[1]).terminal?
94
+ { type: :entity, name: entity_value.text_value.to_sym }
95
+ else
96
+ { type: :entity, number: entity_value.elements[1].text_value.to_i }
97
+ end
98
+ end
99
+ }
100
+ end
101
+
102
+ rule entity_number
103
+ [0-9] 2..4
104
+ end
105
+
106
+ rule entity_name
107
+ 'amp' / 'apos' / 'gt' / 'lt' / 'quot'
108
+ end
109
+
110
+ rule spaces
111
+ ' '+
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,178 @@
1
+ module Asciidoctor
2
+ module Prawn
3
+ class FormattedTextTransform
4
+ def initialize(options = {})
5
+ @merge_adjacent_text_nodes = options.fetch(:merge_adjacent_text_nodes, false)
6
+ if (theme = options[:theme])
7
+ @link_font_color = theme.link_font_color
8
+ @monospaced_font_color = theme.literal_font_color
9
+ @monospaced_font_family = theme.literal_font_family
10
+ @monospaced_font_size = theme.literal_font_size
11
+ #@monospaced_letter_spacing = theme.literal_letter_spacing
12
+ else
13
+ @link_font_color = '0000FF'
14
+ @monospaced_font_color = nil
15
+ @monospaced_font_family = 'Courier'
16
+ @monospaced_font_size = 0.9
17
+ #@monospaced_letter_spacing = -0.1
18
+ end
19
+ end
20
+
21
+ # FIXME might want to pass styles downwards rather than decorating on way up
22
+ def apply(parsed)
23
+ fragments = []
24
+ previous_fragment_is_text = false
25
+ # NOTE using inject is slower than a manual loop
26
+ parsed.each {|node|
27
+ case (node_type = node[:type])
28
+ when :element
29
+ if (tag_name = node[:name]) == :br
30
+ if @merge_adjacent_text_nodes && previous_fragment_is_text
31
+ fragments << { text: %(#{fragments.pop[:text]}\n) }
32
+ else
33
+ fragments << { text: "\n" }
34
+ end
35
+ previous_fragment_is_text = true
36
+ else
37
+ pcdata = node[:pcdata] || []
38
+ attributes = node[:attributes]
39
+ fragments << apply(pcdata).map {|fragment|
40
+ # decorate child fragments with styles from this element
41
+ build_fragment(fragment, tag_name, attributes)
42
+ } unless pcdata.size == 0
43
+ previous_fragment_is_text = false
44
+ end
45
+ when :text, :entity
46
+ node_text = if node_type == :text
47
+ node[:value]
48
+ elsif node_type == :entity
49
+ if (entity_name = node[:name])
50
+ case entity_name
51
+ when :lt
52
+ '<'
53
+ when :gt
54
+ '>'
55
+ when :amp
56
+ '&'
57
+ when :quot
58
+ '"'
59
+ when :apos
60
+ '\''
61
+ end
62
+ else
63
+ [node[:number]].pack('U*')
64
+ # afm fonts do not include a thin space glyph
65
+ # set fallback_fonts to allow glyph to be resolved
66
+ #if (node_number = node[:number]) == 8201
67
+ # ' '
68
+ #else
69
+ # [node_number].pack('U*')
70
+ #end
71
+ end
72
+ end
73
+ if @merge_adjacent_text_nodes && previous_fragment_is_text
74
+ fragments << { text: %(#{fragments.pop[:text]}#{node_text}) }
75
+ else
76
+ fragments << { text: node_text }
77
+ end
78
+ previous_fragment_is_text = true
79
+ end
80
+ }
81
+ fragments.flatten
82
+ end
83
+
84
+ def build_fragment(fragment, tag_name = nil, attrs = {})
85
+ #return { text: fragment } if tag_name.nil?
86
+ styles = (fragment[:styles] ||= ::Set.new)
87
+ case tag_name
88
+ when :b, :strong
89
+ styles << :bold
90
+ when :i, :em
91
+ styles << :italic
92
+ when :code
93
+ fragment[:font] ||= @monospaced_font_family
94
+ if @monospaced_font_size
95
+ fragment[:size] ||= @monospaced_font_size
96
+ end
97
+ #if @monospaced_letter_spacing
98
+ # fragment[:character_spacing] ||= @monospaced_letter_spacing
99
+ #end
100
+ if @monospaced_font_color
101
+ fragment[:color] ||= @monospaced_font_color
102
+ end
103
+ when :color
104
+ if !fragment[:color]
105
+ if (rgb = attrs[:rgb])
106
+ if rgb[0] == '#'
107
+ rgb = rgb[1..-1]
108
+ end
109
+ fragment[:color] = rgb
110
+ elsif (r = attrs[:r]) && (g = attrs[:g]) && (b = attrs[:b])
111
+ fragment[:color] = [r, g, b].map {|e| '%02x' % e.to_i }.join
112
+ elsif (c = attrs[:c]) && (m = attrs[:m]) && (y = attrs[:y]) && (k = attrs[:k])
113
+ fragment[:color] = [c.to_i, m.to_i, y.to_i, k.to_i]
114
+ end
115
+ end
116
+ when :font
117
+ if !fragment[:font] && (value = attrs[:name])
118
+ fragment[:font] = value
119
+ end
120
+ if !fragment[:size] && (value = attrs[:size])
121
+ fragment[:size] = value.to_f
122
+ end
123
+ #if !fragment[:character_spacing] && (value = attrs[:character_spacing])
124
+ # fragment[:character_spacing] = value.to_f
125
+ #end
126
+ when :a, :link
127
+ if !fragment[:anchor] && (value = attrs[:anchor])
128
+ fragment[:anchor] = value
129
+ end
130
+ if !fragment[:link] && (value = attrs[:href])
131
+ fragment[:link] = value
132
+ end
133
+ if !fragment[:local] && (value = attrs[:local])
134
+ fragment[:local] = value
135
+ end
136
+ fragment[:color] ||= @link_font_color
137
+ when :sub
138
+ styles << :subscript
139
+ when :sup
140
+ styles << :superscript
141
+ when :u
142
+ styles << :underline
143
+ when :del, :strikethrough
144
+ styles << :strikethrough
145
+ when :span
146
+ # span logic with normal style parsing
147
+ if (inline_styles = attrs[:style])
148
+ inline_styles.rstrip.chomp(';').split(';').each do |style|
149
+ pname, pvalue = style.split(':', 2)
150
+ case pname
151
+ when 'color'
152
+ fragment[:color] = pvalue.tr(' #', '') unless fragment[:color]
153
+ when 'font-weight'
154
+ if pvalue.lstrip == 'bold'
155
+ styles << :bold
156
+ end
157
+ when 'font-style'
158
+ if pvalue.lstrip == 'italic'
159
+ styles << :italic
160
+ end
161
+ end
162
+ end
163
+ end
164
+
165
+ # quicker span logic that only honors font color
166
+ #if !fragment[:color] && (value = attrs[:style]) && value.start_with?('color:')
167
+ # if value.include?(';')
168
+ # value = value.split(';').first
169
+ # end
170
+ # fragment[:color] = value[6..-1].tr(' #', '')
171
+ #end
172
+ end
173
+ #fragment.delete(:styles) if styles.empty?
174
+ fragment
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,107 @@
1
+ ########################################################################
2
+ #
3
+ # This file was copied from roman-numerals and modified for use with
4
+ # Asciidoctor PDF.
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining
7
+ # a copy of this software and associated documentation files (the
8
+ # "Software"), to deal in the Software without restriction, including
9
+ # without limitation the rights to use, copy, modify, merge, publish,
10
+ # distribute, sublicense, and/or sell copies of the Software, and to
11
+ # permit persons to whom the Software is furnished to do so, subject to
12
+ # the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be
15
+ # included in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+ #
25
+ # Copyright (c) 2011 Andrew Vos
26
+ # Copyright (c) 2014 OpenDevice, Inc.
27
+ #
28
+ ########################################################################
29
+
30
+ module Asciidoctor
31
+ module Pdf
32
+ class RomanNumeral
33
+ BaseDigits = {
34
+ 1 => 'I',
35
+ 4 => 'IV',
36
+ 5 => 'V',
37
+ 9 => 'IX',
38
+ 10 => 'X',
39
+ 40 => 'XL',
40
+ 50 => 'L',
41
+ 90 => 'XC',
42
+ 100 => 'C',
43
+ 400 => 'CD',
44
+ 500 => 'D',
45
+ 900 => 'CM',
46
+ 1000 => 'M'
47
+ }
48
+
49
+ def initialize initial_value, letter_case = nil
50
+ initial_value ||= 1
51
+ if initial_value.is_a? ::Integer
52
+ @integer_value = initial_value
53
+ else
54
+ @integer_value = RomanNumeral.roman_to_int initial_value
55
+ letter_case = :lower if letter_case.nil? && initial_value.upcase != initial_value
56
+ end
57
+ @letter_case = letter_case.nil? ? :upper : letter_case
58
+ end
59
+
60
+ def to_s
61
+ to_r
62
+ end
63
+
64
+ def to_r
65
+ roman = RomanNumeral.int_to_roman @integer_value
66
+ @letter_case == :lower ? roman.downcase : roman
67
+ end
68
+
69
+ def to_i
70
+ @integer_value
71
+ end
72
+
73
+ def next
74
+ RomanNumeral.new @integer_value + 1, @letter_case
75
+ end
76
+
77
+ def next!
78
+ @integer_value += 1
79
+ self
80
+ end
81
+
82
+ def self.int_to_roman value
83
+ result = ''
84
+ BaseDigits.keys.reverse.each do |ival|
85
+ while value >= ival
86
+ value -= ival
87
+ result += BaseDigits[ival]
88
+ end
89
+ end
90
+ result
91
+ end
92
+
93
+ def self.roman_to_int value
94
+ value = value.upcase
95
+ result = 0
96
+ BaseDigits.values.reverse.each do |rval|
97
+ while value.start_with? rval
98
+ offset = rval.length
99
+ value = value[offset..offset]
100
+ result += BaseDigits.key rval
101
+ end
102
+ end
103
+ result
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,103 @@
1
+ require 'yaml'
2
+ require 'ostruct'
3
+ require_relative 'core_ext/ostruct'
4
+
5
+ module Asciidoctor
6
+ module Pdf
7
+ class ThemeLoader
8
+ DataDir = ::File.expand_path ::File.join(::File.dirname(__FILE__), '..', '..', 'data')
9
+ ThemesDir = ::File.join DataDir, 'themes'
10
+ FontsDir = ::File.join DataDir, 'fonts'
11
+
12
+ def self.resolve_theme_file theme_name = nil, theme_path = nil
13
+ theme_name ||= 'default'
14
+ # if .yml extension is given, assume it's a full file name
15
+ if theme_name.end_with? '.yml'
16
+ # FIXME restrict to jail!
17
+ theme_path ? (::File.join theme_path, theme_name) : theme_name
18
+ else
19
+ # QUESTION should we append '-theme.yml' or just '.yml'?
20
+ ::File.expand_path %(#{theme_name}-theme.yml), (theme_path || ThemesDir)
21
+ end
22
+ end
23
+
24
+ def self.load_theme theme_name = nil, theme_path = nil
25
+ load_file (resolve_theme_file theme_name, theme_path)
26
+ end
27
+
28
+ def self.load_file filename
29
+ theme_hash = YAML.load_file filename
30
+ self.new.load theme_hash
31
+ end
32
+
33
+ def load hash
34
+ hash.inject(OpenStruct.new) do |s, (k, v)|
35
+ if v.kind_of? Hash
36
+ v.each do |k2, v2|
37
+ s[%(#{k}_#{k2})] = (k2.end_with? '_color') ? evaluate(v2, s).to_s : evaluate(v2, s)
38
+ end
39
+ else
40
+ s[k] = (k.end_with? '_color') ? evaluate(v, s).to_s : evaluate(v, s)
41
+ end
42
+ s
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def evaluate expr, vars
49
+ case expr
50
+ when String
51
+ evaluate_math(expand_vars(expr, vars))
52
+ when Array
53
+ expr.map {|e| evaluate(e, vars) }
54
+ else
55
+ expr
56
+ end
57
+ end
58
+
59
+ def expand_vars expr, vars
60
+ if expr.include? '$'
61
+ if (expr.start_with? '$') && (expr.match /^\$([a-z0-9_]+)$/)
62
+ vars[$1]
63
+ else
64
+ expr.gsub(/\$([a-z0-9_]+)/) { vars[$1] }
65
+ end
66
+ else
67
+ expr
68
+ end
69
+ end
70
+
71
+ def evaluate_math expr
72
+ return expr unless expr.kind_of? String
73
+ original = expr
74
+ while true
75
+ result = expr.gsub(/(-?\d+(?:\.\d+)?) *([*\/]) *(-?\d+(?:\.\d+)?)/) { $1.to_f.send($2.to_sym, $3.to_f) }
76
+ unchanged = (result == expr)
77
+ expr = result
78
+ break if unchanged
79
+ end
80
+ while true
81
+ result = expr.gsub(/(-?\d+(?:\.\d+)?) *([+\-]) *(-?\d+(?:\.\d+)?)/) { $1.to_f.send($2.to_sym, $3.to_f) }
82
+ unchanged = (result == expr)
83
+ expr = result
84
+ break if unchanged
85
+ end
86
+ if (expr.end_with? ')') && (expr.match /^(round|floor|ceil)\(/)
87
+ op = $1
88
+ offset = op.length + 1
89
+ expr = expr[offset...-1].to_f.send(op.to_sym)
90
+ end
91
+ if original == expr
92
+ expr
93
+ else
94
+ if ((int_val = expr.to_i) == (float_val = expr.to_f))
95
+ int_val
96
+ else
97
+ float_val
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end