asciidoctor-pdf 1.5.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
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