code_slide 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: aebc8a3009b9b263056b648e126f867b1b8dca98
4
+ data.tar.gz: 8f306a98bf274c541c4c65a4d6c5c6c9a9e3c118
5
+ SHA512:
6
+ metadata.gz: f1ea745fc473cabdf4a48679e6da926a8f6074611e3d82ea1054f51eecc00d3a42d6aaff6e66a0b9596a7c3971a504b4c39b8c5ea1b41fcdabb75acac11321d9
7
+ data.tar.gz: ea9222bc9825af6abf019abfee1496db11f507234d72a87e52995e79bcdc86dbec9df4f20cf43fe12ea1db2493e02ca530e6bbe4415f379ff69333621e7ecd5b
@@ -0,0 +1,20 @@
1
+ Copyright 2017 Jamis Buck
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,82 @@
1
+ # CodeSlide
2
+
3
+ A library for turning code snippets into slides. Automatically turn your
4
+ source code into syntax-highlighted PDFs and PNGs. Take the tedium out of
5
+ building your presentation's slide deck!
6
+
7
+
8
+ ## Installation
9
+
10
+ If you want to be able to automatically generate PNG files, you'll need to have
11
+ GhostScript installed (https://ghostscript.com/).
12
+
13
+ Aside from that, installation is as easy as:
14
+
15
+ $ gem install code_slide
16
+
17
+ And you're good to go!
18
+
19
+
20
+ ## Usage
21
+
22
+ Look in the `examples` directory for some demonstrations of CodeSlide. In
23
+ brief, though, it works like this:
24
+
25
+ ```ruby
26
+ require 'code_slide'
27
+
28
+ snippet = CodeSlide::Snippet.new(<<-RUBY, lang: :ruby)
29
+ 1.upto(100) do |n|
30
+ print 'Fizz' if (n % 3).zero?
31
+ print 'Buzz' if (n % 5).zero?
32
+ puts if (n % 3).zero? || (n % 5).zero?
33
+ end
34
+ RUBY
35
+
36
+ snippet.make_pdf('snippet.pdf')
37
+ ```
38
+
39
+ You can also source your snippet from a file directly, even specifying which
40
+ range of lines you want to use:
41
+
42
+ ```ruby
43
+ snippet = CodeSlide::Snippet.from_file('fizz-buzz.rb', start: 5, finish: 15)
44
+ snippet.make_pdf('snippet.pdf')
45
+ ```
46
+
47
+ If you have a (TTF) font you want to use, you can specify the different faces
48
+ in the family, and instruct CodeSlide to use them:
49
+
50
+ ```ruby
51
+ snippet = CodeSlide::Snippet.from_file('fizz-buzz.rb', start: 5, finish: 15)
52
+ snippet.use_font('myfont-regular.ttf',
53
+ bold: 'myfont-bold.ttf',
54
+ italic: 'myfont-italic.ttf',
55
+ bold_italic: 'myfont-bold-italic.ttf')
56
+ snippet.make_pdf('snippet.pdf')
57
+ ```
58
+
59
+ You can choose between the "light" and "dark" themes (or make your own):
60
+
61
+ ```ruby
62
+ snippet = CodeSlide::Snippet.from_file('fizz-buzz.rb', theme: :dark)
63
+ snippet.make_pdf('dark-snippet.pdf')
64
+ ```
65
+
66
+ And if you want to generate a PNG directly, you can do that too (though again,
67
+ you need to have GhostScript installed):
68
+
69
+ ```ruby
70
+ snippet.make_png('snippet.png')
71
+ ```
72
+
73
+
74
+ ## License
75
+
76
+ This software is released under the terms of the MIT license. See the
77
+ `MIT-LICENSE` for full details.
78
+
79
+
80
+ ## Author
81
+
82
+ This software is written and distributed by Jamis Buck <jamis@jamisbuck.org>.
@@ -0,0 +1,2 @@
1
+ require 'code_slide/snippet'
2
+ require 'code_slide/version'
@@ -0,0 +1,134 @@
1
+ require 'coderay'
2
+ require 'nokogiri'
3
+ require 'code_slide/theme_manager'
4
+
5
+ module CodeSlide
6
+ class Analyzer
7
+ attr_reader :elements
8
+ attr_reader :width
9
+ attr_reader :height
10
+ attr_reader :styles
11
+ attr_reader :gutter_width
12
+
13
+ def initialize(text, options = {})
14
+ @styles = options[:styles] ||
15
+ (options[:theme] && ThemeManager.load_theme(options[:theme])) ||
16
+ ThemeManager.load_theme(:light)
17
+
18
+ line_numbers = options[:line_numbers] && :inline
19
+ bold_every = options[:bold_every] || nil
20
+ line_start = options[:line_number_start] || 1
21
+
22
+ text = _preprocess_text(text)
23
+ _compute_gutter(line_numbers, line_start)
24
+ html = _generate_html(text, options[:lang],
25
+ line_numbers, line_start, bold_every)
26
+
27
+ _format_html(html)
28
+ end
29
+
30
+ def _compute_gutter(line_numbers, line_start)
31
+ @gutter_width = 0
32
+ return unless line_numbers
33
+
34
+ max = line_start + height
35
+ @gutter_width = Math.log10(max + 1).ceil + 1
36
+ end
37
+
38
+ def _generate_html(text, lang, line_numbers, line_start, bold_every)
39
+ CodeRay.scan(text, lang).
40
+ html(line_numbers: line_numbers,
41
+ line_number_anchors: false,
42
+ line_number_start: line_start,
43
+ bold_every: bold_every).
44
+ gsub(%r{</strong>}, '</span>').
45
+ gsub(/<strong>/, '<span class="highlight">')
46
+ end
47
+
48
+ # removes leading and trailing blank lines
49
+ def _preprocess_text(text)
50
+ lines = text.lines.map(&:rstrip)
51
+ lines.delete_at(0) while lines[0].empty?
52
+ lines.delete_at(-1) while lines[-1].empty?
53
+
54
+ @width = lines.map(&:length).max
55
+ @height = lines.length
56
+
57
+ lines.join("\n")
58
+ end
59
+
60
+ def _format_html(html)
61
+ doc = Nokogiri::HTML.fragment(html)
62
+
63
+ @current_attributes = [{}]
64
+ @elements = []
65
+
66
+ _format(doc)
67
+ end
68
+
69
+ def _format(parent)
70
+ style = _lookup_class(parent['class'])
71
+ @current_attributes.push(_merge_style(style))
72
+ parent.children.each { |child| _format_node(child) }
73
+ ensure
74
+ attrs = @current_attributes.pop
75
+ @elements.last[:text] << attrs[:after] if attrs[:after]
76
+ end
77
+
78
+ def _format_node(node)
79
+ if node.text?
80
+ @elements << _style(node)
81
+ elsif node.element?
82
+ _format(node)
83
+ else
84
+ raise 'unknown node type'
85
+ end
86
+ end
87
+
88
+ def _style(node)
89
+ text = _text(node)
90
+ attrs = @current_attributes.last
91
+ attrs.merge(text: text)
92
+ end
93
+
94
+ def _merge_style(style)
95
+ styles = @current_attributes.last[:styles]
96
+ if style[:styles]
97
+ styles ||= []
98
+ styles = [*style[:styles], *styles]
99
+ end
100
+
101
+ @current_attributes.last.merge(style).tap do |attrs|
102
+ attrs[:styles] = styles if styles
103
+
104
+ # 'after' is never inherited
105
+ attrs[:after] = nil unless style[:after]
106
+ end
107
+ end
108
+
109
+ def _lookup_class(class_name)
110
+ return (@styles[:default] || {}) unless class_name
111
+
112
+ class_name && (@styles[class_name] || @styles[class_name.to_sym]) ||
113
+ begin
114
+ warn "no definition for #{class_name}"
115
+ { class: class_name }
116
+ end
117
+ end
118
+
119
+ ENTITY = {
120
+ 'lt' => '<',
121
+ 'gt' => '>',
122
+ 'amp' => '&'
123
+ }.freeze
124
+
125
+ def _text(text)
126
+ text.to_s.
127
+ tr(' ', "\u00A0").
128
+ gsub(/\&(.*?);/) do |match|
129
+ entity = Regexp.last_match(1)
130
+ ENTITY[entity.downcase] || match
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,93 @@
1
+ require 'prawn'
2
+
3
+ module CodeSlide
4
+ class PDFFormatter
5
+ def initialize(analyzer)
6
+ @analyzer = analyzer
7
+ end
8
+
9
+ def use_font(path, bold: path, italic: path, bold_italic: path)
10
+ @font = path
11
+ @bold = bold
12
+ @italic = italic
13
+ @bold_italic = bold_italic
14
+
15
+ self
16
+ end
17
+
18
+ def build_pdf(gravity: :center,
19
+ background_color: nil,
20
+ font_size: 16, page_width: 792, page_height: 612)
21
+ Prawn::Document.new(page_size: [page_width, page_height]).tap do |pdf|
22
+ Prawn::Font::AFM.hide_m17n_warning = true
23
+
24
+ _prepare_background(pdf, background_color)
25
+ pdf.font _prepare_font(pdf), size: font_size
26
+ options = _prepare_options(pdf, gravity)
27
+
28
+ box = Prawn::Text::Formatted::Box.new(@analyzer.elements, options)
29
+ box.render
30
+ end
31
+ end
32
+
33
+ def _prepare_background(pdf, bgcolor)
34
+ bgcolor ||= @analyzer.styles[:background] &&
35
+ @analyzer.styles[:background][:color]
36
+ return unless bgcolor
37
+
38
+ pdf.canvas do
39
+ pdf.fill_color bgcolor
40
+ pdf.fill_rectangle [pdf.bounds.left, pdf.bounds.top],
41
+ pdf.bounds.right, pdf.bounds.top
42
+ end
43
+ end
44
+
45
+ def _prepare_font(pdf)
46
+ if @font.nil?
47
+ 'Courier'
48
+ else
49
+ pdf.font_families.update(
50
+ 'Custom' => { normal: @font,
51
+ bold: @bold,
52
+ italic: @italic,
53
+ bold_italic: @bold_italic })
54
+ 'Custom'
55
+ end
56
+ end
57
+
58
+ def _prepare_options(pdf, gravity)
59
+ width = pdf.width_of('M') * @analyzer.width
60
+ height = pdf.height_of('M') * @analyzer.height
61
+
62
+ width += @analyzer.gutter_width
63
+
64
+ {
65
+ document: pdf,
66
+ overflow: :overflow,
67
+ at: _box_position(pdf, width, height, gravity)
68
+ }
69
+ end
70
+
71
+ def _box_position(pdf, width, height, gravity)
72
+ left = 0
73
+ hcenter = (pdf.bounds.width - width) / 2
74
+ right = pdf.bounds.width - width
75
+ top = pdf.bounds.height
76
+ vcenter = pdf.bounds.height - (pdf.bounds.height - height) / 2
77
+ bottom = height
78
+
79
+ case gravity
80
+ when :northwest then [left, top ]
81
+ when :north then [hcenter, top ]
82
+ when :northeast then [right, top ]
83
+ when :west then [left, vcenter]
84
+ when :center then [hcenter, vcenter]
85
+ when :east then [right, vcenter]
86
+ when :southwest then [left, bottom ]
87
+ when :south then [hcenter, bottom ]
88
+ when :southeast then [right, bottom ]
89
+ else raise ArguentError, "unsupported gravity #{gravity.inspect}"
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,12 @@
1
+ module CodeSlide
2
+ class PNGFormatter
3
+ def initialize(pdf_filename)
4
+ @pdf_filename = pdf_filename
5
+ end
6
+
7
+ def generate_png(filename, dpi: 300, keep_pdf: false)
8
+ system 'gs -q -sDEVICE=png16m -dTextAlphaBits=4 ' \
9
+ "-r#{dpi} -o #{filename} #{@pdf_filename}"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,72 @@
1
+ require 'code_slide/analyzer'
2
+ require 'code_slide/pdf_formatter'
3
+ require 'code_slide/png_formatter'
4
+
5
+ module CodeSlide
6
+ class Snippet
7
+ def self.from_file(filename, options = {})
8
+ options = options.dup
9
+
10
+ start = options.delete(:start) || 1
11
+ finish = options.delete(:finish) || -1
12
+
13
+ line_start = options[:line_number_start] || start
14
+
15
+ # assume people number their file lines starting at 1
16
+ start -= 1 if start > 0
17
+ finish -= 1 if finish > 0
18
+
19
+ text = File.read(filename).lines[start..finish].join
20
+ new(text,
21
+ options.merge(lang: options[:lang] || detect_language_type(filename),
22
+ line_number_start: line_start))
23
+ end
24
+
25
+ EXTMAP = { # rubocop:disable Style/MutableConstant
26
+ '.rb' => :ruby,
27
+ '.py' => :python,
28
+ '.c' => :c,
29
+ '.java' => :java
30
+ }
31
+
32
+ def self.detect_language_type(filename)
33
+ EXTMAP[File.extname(filename).downcase]
34
+ end
35
+
36
+ def initialize(snippet, options = {})
37
+ @analyzer = CodeSlide::Analyzer.new(snippet, options)
38
+ use_font(nil)
39
+ end
40
+
41
+ def use_font(path, bold: path, italic: path, bold_italic: path)
42
+ @font = path
43
+ @bold = bold
44
+ @italic = italic
45
+ @bold_italic = bold_italic
46
+
47
+ self
48
+ end
49
+
50
+ def make_pdf(filename, options = {})
51
+ PDFFormatter.new(@analyzer).
52
+ use_font(@font, bold: @bold,
53
+ italic: @italic,
54
+ bold_italic: @bold_italic).
55
+ build_pdf(options).
56
+ render_file(filename)
57
+ end
58
+
59
+ def make_png(filename, options = {})
60
+ options = options.dup
61
+ dpi = options.delete(:dpi)
62
+ keep_pdf = options.delete(:keep_pdf)
63
+
64
+ pdf_name = File.basename(filename, File.extname(filename)) + '.pdf'
65
+
66
+ make_pdf(pdf_name, options)
67
+ PNGFormatter.new(pdf_name).
68
+ generate_png(filename, dpi: dpi)
69
+ File.delete(pdf_name) unless keep_pdf
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,47 @@
1
+ require 'yaml'
2
+
3
+ module CodeSlide
4
+ class ThemeManager
5
+ @search_paths = []
6
+ @search_paths << '.'
7
+ @search_paths << File.join(File.dirname(__FILE__), 'themes')
8
+
9
+ @cached_themes = {}
10
+
11
+ class <<self
12
+ attr_reader :search_paths
13
+
14
+ def load_theme(name)
15
+ @cached_themes[name] ||= begin
16
+ file_name = "#{name}.yml"
17
+ path = @search_paths.
18
+ map { |s| File.join(s, file_name) }.
19
+ find { |s| File.exist?(s) }
20
+
21
+ if path.nil?
22
+ raise ArgumentError, "couldn't find theme #{name.inspect}"
23
+ end
24
+
25
+ _post_process(YAML.load_file(path))
26
+ end
27
+ end
28
+
29
+ def _post_process(hash)
30
+ hash.each.with_object({}) do |(key, value), result|
31
+ key = key.to_sym
32
+ result[key] = _post_process_value(key, value)
33
+ end
34
+ end
35
+
36
+ def _post_process_value(key, value)
37
+ if key == :styles
38
+ value.map(&:to_sym)
39
+ elsif value.is_a?(Hash)
40
+ _post_process(value)
41
+ else
42
+ value
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,48 @@
1
+ # modeled after Atom's "One Dark" syntax theme
2
+ ---
3
+ background:
4
+ color: '282c34'
5
+ default:
6
+ color: 'abb2be'
7
+
8
+ comment:
9
+ color: '5c636f'
10
+ styles: [ italic ]
11
+
12
+ keyword:
13
+ color: 'c57bdb'
14
+ class:
15
+ color: 'd09a6a'
16
+ constant:
17
+ color: 'd09a6a'
18
+ function:
19
+ color: '65b0ec'
20
+ string:
21
+ color: '99c27c'
22
+ delimiter:
23
+ color: '99c27c'
24
+ inline:
25
+ color: '99c27c'
26
+ content:
27
+ color: '99c27c'
28
+ symbol:
29
+ color: '5ab6c1'
30
+ key:
31
+ color: '5ab6c1'
32
+ integer:
33
+ color: 'd09a6a'
34
+ float:
35
+ color: 'd09a6a'
36
+ predefined-constant:
37
+ color: 'd09a6a'
38
+ inline-delimiter:
39
+ color: 'bc5149'
40
+ instance-variable:
41
+ color: 'de6d77'
42
+
43
+ highlight:
44
+ color: '999999'
45
+ styles: [ bold ]
46
+ line-numbers:
47
+ color: '555555'
48
+ after: ' '
@@ -0,0 +1,48 @@
1
+ # modeled after Atom's "One Light" syntax theme
2
+ ---
3
+ background:
4
+ color: 'ffffff'
5
+ default:
6
+ color: '000000'
7
+
8
+ comment:
9
+ color: 'a0a1a7'
10
+ styles: [ italic ]
11
+
12
+ keyword:
13
+ color: 'a42ea2'
14
+ class:
15
+ color: 'c0831e'
16
+ constant:
17
+ color: 'c0831e'
18
+ function:
19
+ color: '447bee'
20
+ string:
21
+ color: '53a053'
22
+ delimiter:
23
+ color: '53a053'
24
+ inline:
25
+ color: '53a053'
26
+ content:
27
+ color: '53a053'
28
+ symbol:
29
+ color: '1485ba'
30
+ key:
31
+ color: '1485ba'
32
+ integer:
33
+ color: '976715'
34
+ float:
35
+ color: '976715'
36
+ predefined-constant:
37
+ color: '976715'
38
+ inline-delimiter:
39
+ color: 'c81846'
40
+ instance-variable:
41
+ color: 'e2574e'
42
+
43
+ highlight:
44
+ color: '999999'
45
+ styles: [ bold ]
46
+ line-numbers:
47
+ color: 'bbbbbb'
48
+ after: ' '
@@ -0,0 +1,3 @@
1
+ module CodeSlide
2
+ VERSION = '1.0.0'.freeze
3
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: code_slide
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jamis Buck
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-01-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: prawn
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: coderay
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: nokogiri
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.6'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.6'
55
+ description: |2
56
+ A library for turning code snippets into slides. Automatically turn your
57
+ source code into syntax-highlighted PDFs and PNGs. Take the tedium out
58
+ of building your presentation's slide deck!
59
+ email:
60
+ - jamis@jamisbuck.org
61
+ executables: []
62
+ extensions: []
63
+ extra_rdoc_files: []
64
+ files:
65
+ - MIT-LICENSE
66
+ - README.md
67
+ - lib/code_slide.rb
68
+ - lib/code_slide/analyzer.rb
69
+ - lib/code_slide/pdf_formatter.rb
70
+ - lib/code_slide/png_formatter.rb
71
+ - lib/code_slide/snippet.rb
72
+ - lib/code_slide/theme_manager.rb
73
+ - lib/code_slide/themes/dark.yml
74
+ - lib/code_slide/themes/light.yml
75
+ - lib/code_slide/version.rb
76
+ homepage: https://github.com/jamis/code_slide
77
+ licenses:
78
+ - MIT
79
+ metadata: {}
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 2.5.1
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: Generate PDF/PNG slides from source code
100
+ test_files: []