kramdown 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of kramdown might be problematic. Click here for more details.

Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTERS +5 -1
  3. data/README.md +5 -1
  4. data/Rakefile +5 -3
  5. data/VERSION +1 -1
  6. data/benchmark/generate_data.rb +1 -1
  7. data/doc/default.template +2 -2
  8. data/doc/index.page +3 -4
  9. data/doc/news.feed +1 -1
  10. data/doc/quickref.page +4 -4
  11. data/doc/sidebar.template +7 -8
  12. data/doc/syntax.page +10 -3
  13. data/doc/tests.page +1 -1
  14. data/lib/kramdown/converter.rb +1 -0
  15. data/lib/kramdown/converter/base.rb +57 -9
  16. data/lib/kramdown/converter/kramdown.rb +4 -2
  17. data/lib/kramdown/converter/pdf.rb +638 -0
  18. data/lib/kramdown/document.rb +1 -1
  19. data/lib/kramdown/element.rb +4 -0
  20. data/lib/kramdown/options.rb +44 -8
  21. data/lib/kramdown/parser/base.rb +4 -2
  22. data/lib/kramdown/parser/gfm.rb +25 -18
  23. data/lib/kramdown/parser/html.rb +2 -2
  24. data/lib/kramdown/parser/kramdown.rb +24 -2
  25. data/lib/kramdown/parser/kramdown/abbreviation.rb +3 -2
  26. data/lib/kramdown/parser/kramdown/autolink.rb +2 -1
  27. data/lib/kramdown/parser/kramdown/blockquote.rb +2 -1
  28. data/lib/kramdown/parser/kramdown/codeblock.rb +4 -2
  29. data/lib/kramdown/parser/kramdown/codespan.rb +2 -1
  30. data/lib/kramdown/parser/kramdown/emphasis.rb +2 -1
  31. data/lib/kramdown/parser/kramdown/extensions.rb +5 -4
  32. data/lib/kramdown/parser/kramdown/footnote.rb +6 -4
  33. data/lib/kramdown/parser/kramdown/header.rb +4 -2
  34. data/lib/kramdown/parser/kramdown/horizontal_rule.rb +2 -1
  35. data/lib/kramdown/parser/kramdown/html_entity.rb +4 -2
  36. data/lib/kramdown/parser/kramdown/link.rb +3 -2
  37. data/lib/kramdown/parser/kramdown/list.rb +9 -5
  38. data/lib/kramdown/parser/kramdown/math.rb +5 -3
  39. data/lib/kramdown/parser/kramdown/paragraph.rb +2 -1
  40. data/lib/kramdown/parser/kramdown/table.rb +5 -3
  41. data/lib/kramdown/parser/kramdown/typographic_symbol.rb +10 -5
  42. data/lib/kramdown/utils.rb +1 -0
  43. data/lib/kramdown/utils/string_scanner.rb +52 -0
  44. data/lib/kramdown/version.rb +1 -1
  45. data/man/man1/kramdown.1 +41 -6
  46. data/test/test_files.rb +2 -2
  47. data/test/test_location.rb +158 -0
  48. data/test/test_string_scanner_kramdown.rb +22 -0
  49. data/test/testcases/block/04_header/with_auto_id_stripping.html +1 -0
  50. data/test/testcases/block/04_header/with_auto_id_stripping.options +1 -0
  51. data/test/testcases/block/04_header/with_auto_id_stripping.text +1 -0
  52. data/test/testcases/span/math/normal.html +2 -1
  53. data/test/testcases/span/math/normal.text +2 -1
  54. data/test/testcases_gfm/hard_line_breaks_off.html +2 -0
  55. data/test/testcases_gfm/hard_line_breaks_off.options +1 -0
  56. data/test/testcases_gfm/hard_line_breaks_off.text +2 -0
  57. metadata +27 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 32280f4fafeca55aeecb04530f975715e811f7f2
4
- data.tar.gz: 093389e9fa3fcf20e987c12043377358ff03d632
3
+ metadata.gz: 762672bf30e0c2bcc71355e3b43edb552dc0680f
4
+ data.tar.gz: ab25e4ed7bc11403cf899f114b6ae468219adced
5
5
  SHA512:
6
- metadata.gz: 62f5d75971e5d2a217d97638e9f961be1961d9d0f860a6b1bef082dd2471ae8207dd6eef5500a277ade27a02a60051c863a2d611f1973a3abfba0adf500b37b6
7
- data.tar.gz: 78b3ec99523df42f35a0c3cbc264c2aa3b15ba4c6a9570b316673edc2d88edcb2c1d9e831afbddcd7c6bbd5ceac695f950bd8f94785275d020f1f7fef3c176ed
6
+ metadata.gz: c97487f81874d9c1e4109e94ec1050997c8c4998b8cc3a3d5c64e43513473e3942111b26060555bf508e3201b672b67ae3db44ebdb2f96ccdac0689abf8c0558
7
+ data.tar.gz: 89837ca84aa43bf437fb3aec9c6ac919cc9f424045746201b5e365b23a11b5962ef0d340357338587e06dc9fa0cb191e52a86d800365ac47b551c9c0486d51c9
@@ -1,11 +1,12 @@
1
1
  Count Name
2
2
  ======= ====
3
- 603 Thomas Leitner <t_leitner@gmx.at>
3
+ 631 Thomas Leitner <t_leitner@gmx.at>
4
4
  6 Gioele Barabucci <gioele@svario.it>
5
5
  4 Ted Pak <powerpak006@gmail.com>
6
6
  4 Arne Brasseur <arne@arnebrasseur.net>
7
7
  3 Henning Perl <perl@fast-sicher.de>
8
8
  3 gettalong <t_leitner@gmx.at>
9
+ 3 Brandur <brandur@mutelight.org>
9
10
  3 Ben Armston <ben.armston@googlemail.com>
10
11
  3 Alex Marandon <contact@alexmarandon.com>
11
12
  2 Bran <m.versum@gmail.com>
@@ -15,9 +16,12 @@
15
16
  1 Tim Bates <tim@rumpuslabs.com>
16
17
  1 Simon Lydell <simon.lydell@gmail.com>
17
18
  1 Postmodern <postmodern.mod3@gmail.com>
19
+ 1 Pete Michaud <michaudp@gmail.com>
18
20
  1 myqlarson <myqlarson@gmail.com>
19
21
  1 Michal Till <michal.till@gmail.com>
20
22
  1 Marcus Stollsteimer <sto.mar@web.de>
23
+ 1 Luca Barbato <luca.barbato@gmail.com>
24
+ 1 Jo Hund <jhund@clearcove.ca>
21
25
  1 John Croisant <jacius@gmail.com>
22
26
  1 Joe Fiorini <joe@faithfulgeek.org>
23
27
  1 Damien Pollet <damien.pollet@gmail.com>
data/README.md CHANGED
@@ -6,7 +6,7 @@ kramdown was originally licensed under the GPL until the 1.0.0 release. However,
6
6
  requests it is now released under the MIT license and therefore can easily be used in commercial
7
7
  projects, too.
8
8
 
9
- However, if you use kramdown in a commerical setting, please consider **contributing back any
9
+ However, if you use kramdown in a commercial setting, please consider **contributing back any
10
10
  changes** for the benefit of the community and/or **making a donation** (see the links in the
11
11
  sidebar on the [kramdown homepage](http://kramdown.rubyforge.org/)!
12
12
 
@@ -30,6 +30,10 @@ supported formats:
30
30
  All the documentation on the available input and output formats is available in the **doc/**
31
31
  directory and online at <http://kramdown.rubyforge.org>.
32
32
 
33
+ Starting from version 1.0.0 kramdown is using a versioning scheme with major, minor and patch parts
34
+ in the version number where the major number changes on backwards-incompatible changes, the minor
35
+ number on the introduction of new features and the patch number on everything else.
36
+
33
37
 
34
38
  ## Usage
35
39
 
data/Rakefile CHANGED
@@ -101,7 +101,7 @@ if defined?(Webgen) && defined?(RDoc::Task)
101
101
  end
102
102
 
103
103
  tt = Rake::TestTask.new do |test|
104
- test.warning = true
104
+ test.warning = false
105
105
  test.libs << 'test'
106
106
  test.test_files = FileList['test/test_*.rb']
107
107
  end
@@ -180,6 +180,7 @@ EOF
180
180
  s.require_path = 'lib'
181
181
  s.executables = ['kramdown']
182
182
  s.default_executable = 'kramdown'
183
+ s.add_development_dependency 'minitest', '~> 5.0'
183
184
  s.add_development_dependency 'coderay', '~> 1.0.0'
184
185
  s.add_development_dependency 'stringex', '~> 1.5.1'
185
186
 
@@ -192,7 +193,7 @@ EOF
192
193
 
193
194
  s.author = 'Thomas Leitner'
194
195
  s.email = 't_leitner@gmx.at'
195
- s.homepage = "http://kramdown.rubyforge.org"
196
+ s.homepage = "http://kramdown.gettalong.org"
196
197
  s.rubyforge_project = 'kramdown'
197
198
  end
198
199
 
@@ -246,7 +247,8 @@ EOF
246
247
 
247
248
  desc "Upload the website to Rubyforge"
248
249
  task :publish_website => ['doc'] do
249
- sh "rsync -avc --delete --exclude 'MathJax' --exclude 'robots.txt' htmldoc/ gettalong@rubyforge.org:/var/www/gforge-projects/kramdown/"
250
+ puts "Transfer manually!!!"
251
+ # sh "rsync -avc --delete --exclude 'MathJax' --exclude 'robots.txt' htmldoc/ gettalong@rubyforge.org:/var/www/gforge-projects/kramdown/"
250
252
  end
251
253
 
252
254
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.0
1
+ 1.3.0
@@ -103,7 +103,7 @@ set title "Execution Time Performance for #{theruby}"
103
103
  set xlabel "File Multiplier (i.e. n times mdbasic.text)"
104
104
  set ylabel "Execution Time in secondes"
105
105
  set key left top
106
- set grid "on"
106
+ set grid
107
107
  set terminal png
108
108
  set output "#{graph_name}"
109
109
  EOF
@@ -8,7 +8,7 @@
8
8
  <meta name="keywords" content="ruby, kramdown, markdown, text markup" />
9
9
  <link href="{relocatable: default.css}" type="text/css" rel="stylesheet" media="screen,projection" />
10
10
  <link href="{relocatable: news.atom}" type="application/atom+xml" rel="alternate" />
11
- <script src="http://kramdown.rubyforge.org/MathJax/MathJax.js" type="text/javascript"></script>
11
+ <script src="http://kramdown.gettalong.org/MathJax/MathJax.js" type="text/javascript"></script>
12
12
  <title>{title:} | kramdown</title>
13
13
  </head>
14
14
  <body>
@@ -44,7 +44,7 @@
44
44
 
45
45
  <footer>
46
46
  <div class="float-left">Copyright © 2009-2013 Thomas Leitner</div>
47
- <div class="float-right">Generated by <a href="http://webgen.rubyforge.org">webgen</a></div>
47
+ <div class="float-right">Generated by <a href="http://webgen.gettalong.org">webgen</a></div>
48
48
  </footer>
49
49
 
50
50
  <!-- Start of StatCounter Code -->
@@ -52,11 +52,10 @@ content_processor.tikz.opts: |
52
52
 
53
53
  ## Bugs, Forums, Mailing Lists
54
54
 
55
- If you have found a bug, you should [report it here][bug_report]. Also, there are [forums][forum]
56
- and [mailing lists][ml] available if you have any questions!
55
+ If you have found a bug, you should [report it here][bug_report]. Also, there is a [mailing
56
+ lists][ml] available if you have any questions!
57
57
 
58
- [bug_report]: http://rubyforge.org/tracker/?atid=28673&group_id=7403&func=browse
59
- [forum]: http://rubyforge.org/forum/?group_id=7403
58
+ [bug_report]: http://github.com/gettalong/kramdown/issues
60
59
  [ml]: http://rubyforge.org/mail/?group_id=7403
61
60
 
62
61
 
@@ -2,7 +2,7 @@
2
2
  title: kramdown News
3
3
  description: kramdown - a fast, pure Ruby Markdown-superset converter
4
4
  author: Thomas Leitner
5
- author_url: http://kramdown.rubyforge.org
5
+ author_url: http://kramdown.gettalong.org
6
6
  entries: {alcn: news/*.html, sort: sort_info, reverse: true, limit: 10}
7
7
  versions:
8
8
  atom:
@@ -467,14 +467,14 @@ A simple link can be created by surrounding the text with square brackets and th
467
467
  parentheses:
468
468
 
469
469
  {kdexample::}
470
- A [link](http://kramdown.rubyforge.org)
470
+ A [link](http://kramdown.gettalong.org)
471
471
  to the kramdown homepage.
472
472
  {kdexample}
473
473
 
474
474
  You can also add title information to the link:
475
475
 
476
476
  {kdexample::}
477
- A [link](http://kramdown.rubyforge.org "hp")
477
+ A [link](http://kramdown.gettalong.org "hp")
478
478
  to the homepage.
479
479
  {kdexample}
480
480
 
@@ -486,7 +486,7 @@ the link URL:
486
486
  A [link][kramdown hp]
487
487
  to the homepage.
488
488
 
489
- [kramdown hp]: http://kramdown.rubyforge.org "hp"
489
+ [kramdown hp]: http://kramdown.gettalong.org "hp"
490
490
  {kdexample}
491
491
 
492
492
  If the link text itself is the reference name, the second set of square brackets can be omitted:
@@ -494,7 +494,7 @@ If the link text itself is the reference name, the second set of square brackets
494
494
  {kdexample::}
495
495
  A link to the [kramdown hp].
496
496
 
497
- [kramdown hp]: http://kramdown.rubyforge.org "hp"
497
+ [kramdown hp]: http://kramdown.gettalong.org "hp"
498
498
  {kdexample}
499
499
 
500
500
  Images can be created in a similar way: just use an exclamation mark before the square brackets. The
@@ -1,7 +1,7 @@
1
1
  <h2>News</h2>
2
2
 
3
- <p>The latest version of kramdown is <span class="inline-important">1.2.0</span> and it was released
4
- on <span class="inline-important">2013-08-31</span></p>
3
+ <p>The latest version of kramdown is <span class="inline-important">1.3.0</span> and it was released
4
+ on <span class="inline-important">2013-12-08</span></p>
5
5
 
6
6
  <p>More <a href="{relocatable: news.html}">news</a>…</p>
7
7
 
@@ -10,12 +10,11 @@ on <span class="inline-important">2013-08-31</span></p>
10
10
  <p>If you like kramdown and would like to support it, you are welcome to make a small
11
11
  donation (PayPal or Pledgie) -- it will surely be appreciated! Thanks!</p>
12
12
 
13
- <form action="https://www.paypal.com/cgi-bin/webscr" method="post">
14
- <input type="hidden" name="cmd" value="_s-xclick" />
15
- <input type="hidden" name="encrypted" value="-----BEGIN PKCS7-----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-----END PKCS7-----
16
- " />
17
- <input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donate_SM.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!" />
18
- <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1" />
13
+ <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
14
+ <input type="hidden" name="cmd" value="_s-xclick">
15
+ <input type="hidden" name="hosted_button_id" value="99HUWKWPMUHWG">
16
+ <input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">
17
+ <img alt="" border="0" src="https://www.paypalobjects.com/de_DE/i/scr/pixel.gif" width="1" height="1">
19
18
  </form>
20
19
 
21
20
  <a href='http://www.pledgie.com/campaigns/16657'><img alt='Click here to lend your support to: kramdown and make a donation at www.pledgie.com !' src='http://www.pledgie.com/campaigns/16657.png?skin_name=chrome' border='0' /></a>
@@ -1544,9 +1544,10 @@ key-value pairs
1544
1544
  ID name
1545
1545
 
1546
1546
  : An ID name is defined by using a hash and then the identifier name which needs to start with an
1547
- ASCII character, optionally followed by other ASCII characters, digits, dashes or colons. This is
1548
- a short hand for the key-value pair `id="IDNAME"` since this is often used. The ID name specifies
1549
- the unique ID of a block or span-level element. For example, an ID name looks like `#myid`.
1547
+ ASCII alphabetic character (A-Z or a-z), optionally followed by other ASCII characters, digits,
1548
+ dashes or colons. This is a short hand for the key-value pair `id="IDNAME"` since this is often
1549
+ used. The ID name specifies the unique ID of a block or span-level element. For example, an ID
1550
+ name looks like `#myid`.
1550
1551
 
1551
1552
  class names
1552
1553
 
@@ -1620,6 +1621,12 @@ Here are some examples for span IALs:
1620
1621
  This *is*{:.underline} some `code`{:#id}{:.class}.
1621
1622
  A [link](test.html){:rel='something'} and some **tools**{:.tools}.
1622
1623
 
1624
+ The special span IAL `{::}` contains no attributes but doesn't generate a warning either. It can be
1625
+ used to separate consecutive elements that would be falsely parsed if not separated. Here is an use
1626
+ case:
1627
+
1628
+ This *is italic*{::}*marked*{:.special} text
1629
+
1623
1630
 
1624
1631
  ## Extensions
1625
1632
 
@@ -24,7 +24,7 @@ If you believe you have found a bug in the implementation, please follow these s
24
24
  fashion, please open a [bug report] and attach two files: one with the text and one with the HTML
25
25
  conversion you think is correct.
26
26
 
27
- [bug report]: http://rubyforge.org/tracker/?atid=28673&group_id=7403&func=browse
27
+ [bug report]: http://github.com/gettalong/kramdown/issues
28
28
 
29
29
 
30
30
  ## Benchmark
@@ -23,6 +23,7 @@ module Kramdown
23
23
  autoload :Kramdown, 'kramdown/converter/kramdown'
24
24
  autoload :Toc, 'kramdown/converter/toc'
25
25
  autoload :RemoveHtmlTags, 'kramdown/converter/remove_html_tags'
26
+ autoload :Pdf, 'kramdown/converter/pdf'
26
27
 
27
28
  end
28
29
 
@@ -59,26 +59,52 @@ module Kramdown
59
59
  end
60
60
  private_class_method(:new, :allocate)
61
61
 
62
+ # Returns whether the template should be applied before the conversion of the tree.
63
+ #
64
+ # Defaults to false.
65
+ def apply_template_before?
66
+ false
67
+ end
68
+
69
+ # Returns whether the template should be applied ater the conversion of the tree.
70
+ #
71
+ # Defaults to true.
72
+ def apply_template_after?
73
+ true
74
+ end
75
+
62
76
  # Convert the element tree +tree+ and return the resulting conversion object (normally a
63
77
  # string) and an array with warning messages. The parameter +options+ specifies the conversion
64
78
  # options that should be used.
65
79
  #
66
80
  # Initializes a new instance of the calling class and then calls the #convert method with
67
- # +tree+ as parameter. If the +template+ option is specified and non-empty, the result is
68
- # rendered into the specified template. The template resolution is done in the following way:
81
+ # +tree+ as parameter.
82
+ #
83
+ # If the +template+ option is specified and non-empty, the template is evaluate with ERB
84
+ # before and/or after the tree conversion depending on the result of #apply_template_before?
85
+ # and #apply_template_after?. If the template is evaluated before, an empty string is used for
86
+ # the body; if evaluated after, the result is used as body. See ::apply_template.
87
+ #
88
+ # The template resolution is done in the following way (for the converter ConverterName):
69
89
  #
70
90
  # 1. Look in the current working directory for the template.
71
91
  #
72
- # 2. Append +.convertername+ (e.g. +.html+) to the template name and look for the resulting
73
- # file in the current working directory.
92
+ # 2. Append +.converter_name+ (e.g. +.html+) to the template name and look for the resulting
93
+ # file in the current working directory (the form +.convertername+ is deprecated).
94
+ #
95
+ # 3. Append +.converter_name+ to the template name and look for it in the kramdown data
96
+ # directory (the form +.convertername+ is deprecated).
74
97
  #
75
- # 3. Append +.convertername+ to the template name and look for it in the kramdown data
76
- # directory.
98
+ # 4. Check if the template name starts with 'string://' and if so, strip this prefix away and
99
+ # use the rest as template.
77
100
  def self.convert(tree, options = {})
78
101
  converter = new(tree, ::Kramdown::Options.merge(options.merge(tree.options[:options] || {})))
102
+
103
+ apply_template(converter, '') if !converter.options[:template].empty? && converter.apply_template_before?
79
104
  result = converter.convert(tree)
80
- result.encode!(tree.options[:encoding]) if result.respond_to?(:encode!)
81
- result = apply_template(converter, result) if !converter.options[:template].empty?
105
+ result.encode!(tree.options[:encoding]) if result.respond_to?(:encode!) && result.encoding != Encoding::BINARY
106
+ result = apply_template(converter, result) if !converter.options[:template].empty? && converter.apply_template_after?
107
+
82
108
  [result, converter.warnings]
83
109
  end
84
110
 
@@ -90,6 +116,9 @@ module Kramdown
90
116
  end
91
117
 
92
118
  # Apply the +template+ using +body+ as the body string.
119
+ #
120
+ # The template is evaluated using ERB and the body is available in the @body instance variable
121
+ # and the converter object in the @converter instance variable.
93
122
  def self.apply_template(converter, body) # :nodoc:
94
123
  erb = ERB.new(get_template(converter.options[:template]))
95
124
  obj = Object.new
@@ -99,7 +128,8 @@ module Kramdown
99
128
  end
100
129
 
101
130
  # Return the template specified by +template+.
102
- def self.get_template(template) # :nodoc:
131
+ def self.get_template(template)
132
+ #DEPRECATED: use content of #get_template_new in 2.0
103
133
  format_ext = '.' + self.name.split(/::/).last.downcase
104
134
  shipped = File.join(::Kramdown.data_dir, template + format_ext)
105
135
  if File.exist?(template)
@@ -108,6 +138,24 @@ module Kramdown
108
138
  File.read(template + format_ext)
109
139
  elsif File.exist?(shipped)
110
140
  File.read(shipped)
141
+ elsif template.start_with?('string://')
142
+ template.sub(/\Astring:\/\//, '')
143
+ else
144
+ get_template_new(template)
145
+ end
146
+ end
147
+
148
+ def self.get_template_new(template) # :nodoc:
149
+ format_ext = '.' + ::Kramdown::Utils.snake_case(self.name.split(/::/).last)
150
+ shipped = File.join(::Kramdown.data_dir, template + format_ext)
151
+ if File.exist?(template)
152
+ File.read(template)
153
+ elsif File.exist?(template + format_ext)
154
+ File.read(template + format_ext)
155
+ elsif File.exist?(shipped)
156
+ File.read(shipped)
157
+ elsif template.start_with?('string://')
158
+ template.sub(/\Astring:\/\//, '')
111
159
  else
112
160
  raise "The specified template file #{template} does not exist"
113
161
  end
@@ -321,11 +321,13 @@ module Kramdown
321
321
  end
322
322
 
323
323
  def convert_em(el, opts)
324
- "*#{inner(el, opts)}*"
324
+ "*#{inner(el, opts)}*" +
325
+ (opts[:next] && [:em, :strong].include?(opts[:next].type) && !ial_for_element(el) ? '{::}' : '')
325
326
  end
326
327
 
327
328
  def convert_strong(el, opts)
328
- "**#{inner(el, opts)}**"
329
+ "**#{inner(el, opts)}**" +
330
+ (opts[:next] && [:em, :strong].include?(opts[:next].type) && !ial_for_element(el) ? '{::}' : '')
329
331
  end
330
332
 
331
333
  def convert_entity(el, opts)
@@ -0,0 +1,638 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ #--
4
+ # Copyright (C) 2009-2013 Thomas Leitner <t_leitner@gmx.at>
5
+ #
6
+ # This file is part of kramdown which is licensed under the MIT.
7
+ #++
8
+ #
9
+
10
+ require 'prawn'
11
+ require 'kramdown/utils/entities'
12
+ require 'open-uri'
13
+
14
+ module Kramdown
15
+
16
+ module Converter
17
+
18
+ # Converts an element tree to a PDF using the prawn PDF library.
19
+ #
20
+ # This basic version provides a nice starting point for customizations but can also be used
21
+ # directly.
22
+ #
23
+ # There can be the following two methods for each element type: render_TYPE(el, opts) and
24
+ # TYPE_options(el, opts) where +el+ is a kramdown element and +opts+ an hash with rendering
25
+ # options.
26
+ #
27
+ # The render_TYPE(el, opts) is used for rendering the specific element. If the element is a span
28
+ # element, it should return a hash or an array of hashes that can be used by the #formatted_text
29
+ # method of Prawn::Document. This method can then be used in block elements to actually render
30
+ # the span elements.
31
+ #
32
+ # The rendering options are passed from the parent to its child elements. This allows one to
33
+ # define general options at the top of the tree (the root element) that can later be changed or
34
+ # amended.
35
+ #
36
+ #
37
+ # Currently supports the conversion of all elements except those of the following types:
38
+ #
39
+ # :html_element, :img, :footnote
40
+ #
41
+ #
42
+ class Pdf < Base
43
+
44
+ include Prawn::Measurements
45
+
46
+ def initialize(root, options)
47
+ super
48
+ @stack = []
49
+ @dests = {}
50
+ end
51
+
52
+ # PDF templates are applied before conversion. They should contain code to augment the
53
+ # converter object (i.e. to override the methods).
54
+ def apply_template_before?
55
+ true
56
+ end
57
+
58
+ # Returns +false+.
59
+ def apply_template_after?
60
+ false
61
+ end
62
+
63
+ DISPATCHER_RENDER = Hash.new {|h,k| h[k] = "render_#{k}"} #:nodoc:
64
+ DISPATCHER_OPTIONS = Hash.new {|h,k| h[k] = "#{k}_options"} #:nodoc:
65
+
66
+ # Invoke the special rendering method for the given element +el+.
67
+ #
68
+ # A PDF destination is also added at the current location if th element has an ID or if the
69
+ # element is of type :header and the :auto_ids option is set.
70
+ def convert(el, opts = {})
71
+ id = el.attr['id']
72
+ id = generate_id(el.options[:raw_text]) if !id && @options[:auto_ids] && el.type == :header
73
+ if !id.to_s.empty? && !@dests.has_key?(id)
74
+ @pdf.add_dest(id, @pdf.dest_xyz(0, @pdf.y))
75
+ @dests[id] = @pdf.dest_xyz(0, @pdf.y)
76
+ end
77
+ send(DISPATCHER_RENDER[el.type], el, opts)
78
+ end
79
+
80
+ protected
81
+
82
+ # Render the children of this element with the given options and return the results as array.
83
+ #
84
+ # Each time a child is rendered, the +TYPE_options+ method is invoked (if it exists) to get
85
+ # the specific options for the element with which the given options are updated.
86
+ def inner(el, opts)
87
+ @stack.push([el, opts])
88
+ result = el.children.map do |inner_el|
89
+ options = opts.dup
90
+ options.update(send(DISPATCHER_OPTIONS[inner_el.type], inner_el, options))
91
+ convert(inner_el, options)
92
+ end.flatten.compact
93
+ @stack.pop
94
+ result
95
+ end
96
+
97
+
98
+ # ----------------------------
99
+ # :section: Element rendering methods
100
+ # ----------------------------
101
+
102
+
103
+ def root_options(root, opts)
104
+ {:font => 'Times-Roman', :size => 12, :leading => 2}
105
+ end
106
+
107
+ def render_root(root, opts)
108
+ @pdf = setup_document(root)
109
+ inner(root, root_options(root, opts))
110
+ create_outline(root)
111
+ finish_document(root)
112
+ @pdf.render
113
+ end
114
+
115
+ def header_options(el, opts)
116
+ size = opts[:size] * 1.15**(6 - el.options[:level])
117
+ {
118
+ :font => "Helvetica", :styles => (opts[:styles] || []) + [:bold],
119
+ :size => size, :bottom_padding => opts[:size], :top_padding => opts[:size]
120
+ }
121
+ end
122
+
123
+ def render_header(el, opts)
124
+ render_padded_and_formatted_text(el, opts)
125
+ end
126
+
127
+ def p_options(el, opts)
128
+ bpad = (el.options[:transparent] ? opts[:leading] : opts[:size])
129
+ {:align => :justify, :bottom_padding => bpad}
130
+ end
131
+
132
+ def render_p(el, opts)
133
+ if el.children.size == 1 && el.children.first.type == :img
134
+ render_standalone_image(el, opts)
135
+ else
136
+ render_padded_and_formatted_text(el, opts)
137
+ end
138
+ end
139
+
140
+ def render_standalone_image(el, opts)
141
+ img = el.children.first
142
+
143
+ if img.attr['src'].empty?
144
+ warning("Rendering an image without a source is not possible")
145
+ return nil
146
+ elsif img.attr['src'] !~ /\.jpe?g$|\.png$/
147
+ warning("Cannot render images other than JPEG or PNG, got #{img.attr['src']}")
148
+ return nil
149
+ end
150
+
151
+ img_dirs = (@options[:image_directories] || ['.']).dup
152
+ begin
153
+ img_path = File.join(img_dirs.shift, img.attr['src'])
154
+ image_obj, image_info = @pdf.build_image_object(open(img_path))
155
+ rescue
156
+ img_dirs.empty? ? raise : retry
157
+ end
158
+
159
+ options = {:position => :center}
160
+ if img.attr['height'] && img.attr['height'] =~ /px$/
161
+ options[:height] = img.attr['height'].to_i / (@options[:image_dpi] || 150.0) * 72
162
+ elsif img.attr['width'] && img.attr['width'] =~ /px$/
163
+ options[:width] = img.attr['width'].to_i / (@options[:image_dpi] || 150.0) * 72
164
+ else
165
+ options[:scale] =[(@pdf.bounds.width - mm2pt(20)) / image_info.width.to_f, 1].min
166
+ end
167
+
168
+ if img.attr['class'] =~ /\bright\b/
169
+ options[:position] = :right
170
+ @pdf.float { @pdf.embed_image(image_obj, image_info, options) }
171
+ else
172
+ with_block_padding(el, opts) do
173
+ @pdf.embed_image(image_obj, image_info, options)
174
+ end
175
+ end
176
+ end
177
+
178
+ def blockquote_options(el, opts)
179
+ {:styles => [:italic]}
180
+ end
181
+
182
+ def render_blockquote(el, opts)
183
+ @pdf.indent(mm2pt(10), mm2pt(10)) { inner(el, opts) }
184
+ end
185
+
186
+ def ul_options(el, opts)
187
+ {:bottom_padding => opts[:size]}
188
+ end
189
+
190
+ def render_ul(el, opts)
191
+ with_block_padding(el, opts) do
192
+ el.children.each do |li|
193
+ @pdf.float { @pdf.formatted_text([text_hash("•", opts)]) }
194
+ @pdf.indent(mm2pt(6)) { convert(li, opts) }
195
+ end
196
+ end
197
+ end
198
+
199
+ def ol_options(el, opts)
200
+ {:bottom_padding => opts[:size]}
201
+ end
202
+
203
+ def render_ol(el, opts)
204
+ with_block_padding(el, opts) do
205
+ el.children.each_with_index do |li, index|
206
+ @pdf.float { @pdf.formatted_text([text_hash("#{index+1}.", opts)]) }
207
+ @pdf.indent(mm2pt(6)) { convert(li, opts) }
208
+ end
209
+ end
210
+ end
211
+
212
+ def li_options(el, opts)
213
+ {}
214
+ end
215
+
216
+ def render_li(el, opts)
217
+ inner(el, opts)
218
+ end
219
+
220
+ def dl_options(el, opts)
221
+ {}
222
+ end
223
+
224
+ def render_dl(el, opts)
225
+ inner(el, opts)
226
+ end
227
+
228
+ def dt_options(el, opts)
229
+ {:styles => (opts[:styles] || []) + [:bold], :bottom_padding => 0}
230
+ end
231
+
232
+ def render_dt(el, opts)
233
+ render_padded_and_formatted_text(el, opts)
234
+ end
235
+
236
+ def dd_options(el, opts)
237
+ {}
238
+ end
239
+
240
+ def render_dd(el, opts)
241
+ @pdf.indent(mm2pt(10)) { inner(el, opts) }
242
+ end
243
+
244
+ def math_options(el, opts)
245
+ {}
246
+ end
247
+
248
+ def render_math(el, opts)
249
+ if el.options[:category] == :block
250
+ @pdf.formatted_text([{:text => el.value}], block_hash(opts))
251
+ else
252
+ {:text => el.value}
253
+ end
254
+ end
255
+
256
+ def hr_options(el, opts)
257
+ {:top_padding => opts[:size], :bottom_padding => opts[:size]}
258
+ end
259
+
260
+ def render_hr(el, opts)
261
+ with_block_padding(el, opts) do
262
+ @pdf.stroke_horizontal_line(@pdf.bounds.left + mm2pt(5), @pdf.bounds.right - mm2pt(5))
263
+ end
264
+ end
265
+
266
+ def codeblock_options(el, opts)
267
+ {
268
+ :font => 'Courier', :color => '880000',
269
+ :bottom_padding => opts[:size]
270
+ }
271
+ end
272
+
273
+ def render_codeblock(el, opts)
274
+ with_block_padding(el, opts) do
275
+ @pdf.formatted_text([text_hash(el.value, opts, false)], block_hash(opts))
276
+ end
277
+ end
278
+
279
+ def table_options(el, opts)
280
+ {:bottom_padding => opts[:size]}
281
+ end
282
+
283
+ def render_table(el, opts)
284
+ data = []
285
+ el.children.each do |container|
286
+ container.children.each do |row|
287
+ data << []
288
+ row.children.each do |cell|
289
+ if cell.children.any? {|child| child.options[:category] == :block}
290
+ warning("Can't render tables with cells containing block elements")
291
+ return
292
+ end
293
+ cell_data = inner(cell, opts)
294
+ data.last << cell_data.map {|c| c[:text]}.join('')
295
+ end
296
+ end
297
+ end
298
+ with_block_padding(el, opts) do
299
+ @pdf.table(data, :width => @pdf.bounds.right) do
300
+ el.options[:alignment].each_with_index do |alignment, index|
301
+ columns(index).align = alignment unless alignment == :default
302
+ end
303
+ end
304
+ end
305
+ end
306
+
307
+
308
+
309
+ def text_options(el, opts)
310
+ {}
311
+ end
312
+
313
+ def render_text(el, opts)
314
+ text_hash(el.value.to_s, opts)
315
+ end
316
+
317
+ def em_options(el, opts)
318
+ if opts[:styles] && opts[:styles].include?(:italic)
319
+ {:styles => opts[:styles].reject {|i| i == :italic}}
320
+ else
321
+ {:styles => (opts[:styles] || []) << :italic}
322
+ end
323
+ end
324
+
325
+ def strong_options(el, opts)
326
+ {:styles => (opts[:styles] || []) + [:bold]}
327
+ end
328
+
329
+ def a_options(el, opts)
330
+ hash = {:color => '000088'}
331
+ if el.attr['href'].start_with?('#')
332
+ hash[:anchor] = el.attr['href'].sub(/\A#/, '')
333
+ else
334
+ hash[:link] = el.attr['href']
335
+ end
336
+ hash
337
+ end
338
+
339
+ def render_em(el, opts)
340
+ inner(el, opts)
341
+ end
342
+ alias_method :render_strong, :render_em
343
+ alias_method :render_a, :render_em
344
+
345
+ def codespan_options(el, opts)
346
+ {:font => 'Courier', :color => '880000'}
347
+ end
348
+
349
+ def render_codespan(el, opts)
350
+ text_hash(el.value, opts)
351
+ end
352
+
353
+ def br_options(el, opts)
354
+ {}
355
+ end
356
+
357
+ def render_br(el, opts)
358
+ text_hash("\n", opts, false)
359
+ end
360
+
361
+ def smart_quote_options(el, opts)
362
+ {}
363
+ end
364
+
365
+ def render_smart_quote(el, opts)
366
+ text_hash(smart_quote_entity(el).char, opts)
367
+ end
368
+
369
+ def typographic_sym_options(el, opts)
370
+ {}
371
+ end
372
+
373
+ def render_typographic_sym(el, opts)
374
+ str = if el.value == :laquo_space
375
+ ::Kramdown::Utils::Entities.entity('laquo').char +
376
+ ::Kramdown::Utils::Entities.entity('nbsp').char
377
+ elsif el.value == :raquo_space
378
+ ::Kramdown::Utils::Entities.entity('raquo').char +
379
+ ::Kramdown::Utils::Entities.entity('nbsp').char
380
+ else
381
+ ::Kramdown::Utils::Entities.entity(el.value.to_s).char
382
+ end
383
+ text_hash(str, opts)
384
+ end
385
+
386
+ def entity_options(el, opts)
387
+ {}
388
+ end
389
+
390
+ def render_entity(el, opts)
391
+ text_hash(el.value.char, opts)
392
+ end
393
+
394
+ def abbreviation_options(el, opts)
395
+ {}
396
+ end
397
+
398
+ def render_abbreviation(el, opts)
399
+ text_hash(el.value, opts)
400
+ end
401
+
402
+ def img_options(el, opts)
403
+ {}
404
+ end
405
+
406
+ def render_img(el, *args) #:nodoc:
407
+ warning("Rendering span images is not supported for PDF converter")
408
+ nil
409
+ end
410
+
411
+
412
+
413
+ def xml_comment_options(el, opts) #:nodoc:
414
+ {}
415
+ end
416
+ alias_method :xml_pi_options, :xml_comment_options
417
+ alias_method :comment_options, :xml_comment_options
418
+ alias_method :blank_options, :xml_comment_options
419
+ alias_method :footnote_options, :xml_comment_options
420
+ alias_method :raw_options, :xml_comment_options
421
+ alias_method :html_element_options, :xml_comment_options
422
+
423
+ def render_xml_comment(el, opts) #:nodoc:
424
+ # noop
425
+ end
426
+ alias_method :render_xml_pi, :render_xml_comment
427
+ alias_method :render_comment, :render_xml_comment
428
+ alias_method :render_blank, :render_xml_comment
429
+
430
+ def render_footnote(el, *args) #:nodoc:
431
+ warning("Rendering #{el.type} not supported for PDF converter")
432
+ nil
433
+ end
434
+ alias_method :render_raw, :render_footnote
435
+ alias_method :render_html_element, :render_footnote
436
+
437
+
438
+ # ----------------------------
439
+ # :section: Organizational methods
440
+ #
441
+ # These methods are used, for example, to up the needed Prawn::Document instance or to create
442
+ # a PDF outline.
443
+ # ----------------------------
444
+
445
+
446
+ # This module gets mixed into the Prawn::Document instance.
447
+ module PrawnDocumentExtension
448
+
449
+ # Extension for the formatted box class to recognize images and move text around them.
450
+ module CustomBox
451
+
452
+ def available_width
453
+ return super unless @document.respond_to?(:converter) && @document.converter
454
+
455
+ @document.image_floats.each do |pn, x, y, w, h|
456
+ next if @document.page_number != pn
457
+ if @at[1] + @baseline_y <= y - @document.bounds.absolute_bottom &&
458
+ (@at[1] + @baseline_y + @arranger.max_line_height + @leading >= y - h - @document.bounds.absolute_bottom)
459
+ return @width - w
460
+ end
461
+ end
462
+
463
+ return super
464
+ end
465
+
466
+ end
467
+
468
+ Prawn::Text::Formatted::Box.extensions << CustomBox
469
+
470
+ # Access the converter instance from within Prawn
471
+ attr_accessor :converter
472
+
473
+ def image_floats
474
+ @image_floats ||= []
475
+ end
476
+
477
+ # Override image embedding method for adding image positions to #image_floats.
478
+ def embed_image(pdf_obj, info, options)
479
+ # find where the image will be placed and how big it will be
480
+ w,h = info.calc_image_dimensions(options)
481
+
482
+ if options[:at]
483
+ x,y = map_to_absolute(options[:at])
484
+ else
485
+ x,y = image_position(w,h,options)
486
+ move_text_position h
487
+ end
488
+
489
+ #--> This part is new
490
+ if options[:position] == :right
491
+ image_floats << [page_number, x - 15, y, w + 15, h + 15]
492
+ end
493
+
494
+ # add a reference to the image object to the current page
495
+ # resource list and give it a label
496
+ label = "I#{next_image_id}"
497
+ state.page.xobjects.merge!(label => pdf_obj)
498
+
499
+ # add the image to the current page
500
+ instruct = "\nq\n%.3f 0 0 %.3f %.3f %.3f cm\n/%s Do\nQ"
501
+ add_content instruct % [ w, h, x, y - h, label ]
502
+ end
503
+
504
+ end
505
+
506
+
507
+ # Return a hash with options that are suitable for Prawn::Document.new.
508
+ #
509
+ # Used in #setup_document.
510
+ def document_options(root)
511
+ {
512
+ :page_size => 'A4', :page_layout => :portrait, :margin => mm2pt(20),
513
+ :info => {
514
+ :Creator => 'kramdown PDF converter',
515
+ :CreationDate => Time.now
516
+ },
517
+ :compress => true, :optimize_objects => true
518
+ }
519
+ end
520
+
521
+ # Create a Prawn::Document object and return it.
522
+ #
523
+ # Can be used to define repeatable content or register fonts.
524
+ #
525
+ # Used in #render_root.
526
+ def setup_document(root)
527
+ doc = Prawn::Document.new(document_options(root))
528
+ doc.extend(PrawnDocumentExtension)
529
+ doc.converter = self
530
+ doc
531
+ end
532
+
533
+ #
534
+ #
535
+ # Used in #render_root.
536
+ def finish_document(root)
537
+ # no op
538
+ end
539
+
540
+ # Create the PDF outline from the header elements in the TOC.
541
+ def create_outline(root)
542
+ patch_outline_object
543
+ toc = ::Kramdown::Converter::Toc.convert(root).first
544
+
545
+ text_of_header = lambda do |el|
546
+ if el.type == :text
547
+ el.value
548
+ else
549
+ el.children.map {|c| text_of_header.call(c)}.join('')
550
+ end
551
+ end
552
+
553
+ add_section = lambda do |item, parent|
554
+ text = text_of_header.call(item.value)
555
+ destination = @dests[item.attr[:id]]
556
+ if !parent
557
+ @pdf.outline.page(:title => text, :destination => destination)
558
+ else
559
+ @pdf.outline.add_subsection_to(parent) do
560
+ @pdf.outline.page(:title => text, :destination => destination)
561
+ end
562
+ end
563
+ item.children.each {|c| add_section.call(c, text)}
564
+ end
565
+
566
+ toc.children.each do |item|
567
+ add_section.call(item, nil)
568
+ end
569
+ end
570
+
571
+ # Patch the pdf_document.outline object to accept arbitrary destinations.
572
+ def patch_outline_object
573
+ def (@pdf.outline).create_outline_item(title, options)
574
+ outline_item = ::Prawn::OutlineItem.new(title, parent, options)
575
+
576
+ case options[:destination]
577
+ when Integer
578
+ page_index = options[:destination] - 1
579
+ outline_item.dest = [document.state.pages[page_index].dictionary, :Fit]
580
+ when Array
581
+ outline_item.dest = options[:destination]
582
+ end
583
+
584
+ outline_item.prev = prev if @prev
585
+ items[title] = document.ref!(outline_item)
586
+ end
587
+ end
588
+
589
+
590
+ # ----------------------------
591
+ # :section: Helper methods
592
+ # ----------------------------
593
+
594
+
595
+ # Move the prawn document cursor down before and/or after yielding the given block.
596
+ #
597
+ # The :top_padding and :bottom_padding options are used for determinig the padding amount.
598
+ def with_block_padding(el, opts)
599
+ @pdf.move_down(opts[:top_padding]) if opts.has_key?(:top_padding)
600
+ yield
601
+ @pdf.move_down(opts[:bottom_padding]) if opts.has_key?(:bottom_padding)
602
+ end
603
+
604
+ # Render the children of the given element as formatted text and respect the top/bottom
605
+ # padding (see #with_block_padding).
606
+ def render_padded_and_formatted_text(el, opts)
607
+ with_block_padding(el, opts) { @pdf.formatted_text(inner(el, opts), block_hash(opts)) }
608
+ end
609
+
610
+ # Helper function that returns a hash with valid "formatted text" options.
611
+ #
612
+ # The +text+ parameter is used as value for the :text key and if +squeeze_whitespace+ is
613
+ # +true+, all whitespace is converted into spaces.
614
+ def text_hash(text, opts, squeeze_whitespace = true)
615
+ text = text.gsub(/\s+/, ' ') if squeeze_whitespace
616
+ hash = {:text => text}
617
+ [:styles, :size, :character_spacing, :font, :color, :link,
618
+ :anchor, :draw_text_callback, :callback].each do |key|
619
+ hash[key] = opts[key] if opts.has_key?(key)
620
+ end
621
+ hash
622
+ end
623
+
624
+ # Helper function that returns a hash with valid options for the prawn #text_box extracted
625
+ # from the given options.
626
+ def block_hash(opts)
627
+ hash = {}
628
+ [:align, :valign, :mode, :final_gap, :leading, :fallback_fonts,
629
+ :direction, :indent_paragraphs].each do |key|
630
+ hash[key] = opts[key] if opts.has_key?(key)
631
+ end
632
+ hash
633
+ end
634
+
635
+ end
636
+
637
+ end
638
+ end