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.
- checksums.yaml +4 -4
- data/CONTRIBUTERS +5 -1
- data/README.md +5 -1
- data/Rakefile +5 -3
- data/VERSION +1 -1
- data/benchmark/generate_data.rb +1 -1
- data/doc/default.template +2 -2
- data/doc/index.page +3 -4
- data/doc/news.feed +1 -1
- data/doc/quickref.page +4 -4
- data/doc/sidebar.template +7 -8
- data/doc/syntax.page +10 -3
- data/doc/tests.page +1 -1
- data/lib/kramdown/converter.rb +1 -0
- data/lib/kramdown/converter/base.rb +57 -9
- data/lib/kramdown/converter/kramdown.rb +4 -2
- data/lib/kramdown/converter/pdf.rb +638 -0
- data/lib/kramdown/document.rb +1 -1
- data/lib/kramdown/element.rb +4 -0
- data/lib/kramdown/options.rb +44 -8
- data/lib/kramdown/parser/base.rb +4 -2
- data/lib/kramdown/parser/gfm.rb +25 -18
- data/lib/kramdown/parser/html.rb +2 -2
- data/lib/kramdown/parser/kramdown.rb +24 -2
- data/lib/kramdown/parser/kramdown/abbreviation.rb +3 -2
- data/lib/kramdown/parser/kramdown/autolink.rb +2 -1
- data/lib/kramdown/parser/kramdown/blockquote.rb +2 -1
- data/lib/kramdown/parser/kramdown/codeblock.rb +4 -2
- data/lib/kramdown/parser/kramdown/codespan.rb +2 -1
- data/lib/kramdown/parser/kramdown/emphasis.rb +2 -1
- data/lib/kramdown/parser/kramdown/extensions.rb +5 -4
- data/lib/kramdown/parser/kramdown/footnote.rb +6 -4
- data/lib/kramdown/parser/kramdown/header.rb +4 -2
- data/lib/kramdown/parser/kramdown/horizontal_rule.rb +2 -1
- data/lib/kramdown/parser/kramdown/html_entity.rb +4 -2
- data/lib/kramdown/parser/kramdown/link.rb +3 -2
- data/lib/kramdown/parser/kramdown/list.rb +9 -5
- data/lib/kramdown/parser/kramdown/math.rb +5 -3
- data/lib/kramdown/parser/kramdown/paragraph.rb +2 -1
- data/lib/kramdown/parser/kramdown/table.rb +5 -3
- data/lib/kramdown/parser/kramdown/typographic_symbol.rb +10 -5
- data/lib/kramdown/utils.rb +1 -0
- data/lib/kramdown/utils/string_scanner.rb +52 -0
- data/lib/kramdown/version.rb +1 -1
- data/man/man1/kramdown.1 +41 -6
- data/test/test_files.rb +2 -2
- data/test/test_location.rb +158 -0
- data/test/test_string_scanner_kramdown.rb +22 -0
- data/test/testcases/block/04_header/with_auto_id_stripping.html +1 -0
- data/test/testcases/block/04_header/with_auto_id_stripping.options +1 -0
- data/test/testcases/block/04_header/with_auto_id_stripping.text +1 -0
- data/test/testcases/span/math/normal.html +2 -1
- data/test/testcases/span/math/normal.text +2 -1
- data/test/testcases_gfm/hard_line_breaks_off.html +2 -0
- data/test/testcases_gfm/hard_line_breaks_off.options +1 -0
- data/test/testcases_gfm/hard_line_breaks_off.text +2 -0
- metadata +27 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 762672bf30e0c2bcc71355e3b43edb552dc0680f
|
4
|
+
data.tar.gz: ab25e4ed7bc11403cf899f114b6ae468219adced
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c97487f81874d9c1e4109e94ec1050997c8c4998b8cc3a3d5c64e43513473e3942111b26060555bf508e3201b672b67ae3db44ebdb2f96ccdac0689abf8c0558
|
7
|
+
data.tar.gz: 89837ca84aa43bf437fb3aec9c6ac919cc9f424045746201b5e365b23a11b5962ef0d340357338587e06dc9fa0cb191e52a86d800365ac47b551c9c0486d51c9
|
data/CONTRIBUTERS
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
Count Name
|
2
2
|
======= ====
|
3
|
-
|
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
|
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 =
|
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.
|
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
|
-
|
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.
|
1
|
+
1.3.0
|
data/benchmark/generate_data.rb
CHANGED
@@ -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
|
106
|
+
set grid
|
107
107
|
set terminal png
|
108
108
|
set output "#{graph_name}"
|
109
109
|
EOF
|
data/doc/default.template
CHANGED
@@ -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.
|
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.
|
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 -->
|
data/doc/index.page
CHANGED
@@ -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
|
56
|
-
|
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://
|
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
|
|
data/doc/news.feed
CHANGED
@@ -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.
|
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:
|
data/doc/quickref.page
CHANGED
@@ -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.
|
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.
|
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.
|
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.
|
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
|
data/doc/sidebar.template
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
<h2>News</h2>
|
2
2
|
|
3
|
-
<p>The latest version of kramdown is <span class="inline-important">1.
|
4
|
-
on <span class="inline-important">2013-08
|
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="
|
16
|
-
"
|
17
|
-
<
|
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>
|
data/doc/syntax.page
CHANGED
@@ -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,
|
1548
|
-
a short hand for the key-value pair `id="IDNAME"` since this is often
|
1549
|
-
the unique ID of a block or span-level element. For example, an ID
|
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
|
|
data/doc/tests.page
CHANGED
@@ -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://
|
27
|
+
[bug report]: http://github.com/gettalong/kramdown/issues
|
28
28
|
|
29
29
|
|
30
30
|
## Benchmark
|
data/lib/kramdown/converter.rb
CHANGED
@@ -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.
|
68
|
-
#
|
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 +.
|
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
|
-
#
|
76
|
-
#
|
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)
|
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
|