prawn-icon 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
data/lib/prawn/icon.rb ADDED
@@ -0,0 +1,156 @@
1
+ # encoding: utf-8
2
+ #
3
+ # icon.rb: Prawn icon functionality.
4
+ #
5
+ # Copyright October 2014, Jesse Doyle. All rights reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+
9
+ require_relative 'icon/font_data'
10
+ require_relative 'icon/parser'
11
+
12
+ module Prawn
13
+ module Errors
14
+ # Error raised when an icon glyph is not found
15
+ #
16
+ IconNotFound = Class.new(StandardError)
17
+
18
+ # Error raised when an icon key is not provided
19
+ #
20
+ IconKeyEmpty = Class.new(StandardError)
21
+ end
22
+
23
+ # Easy icon font usage within Prawn. Currently
24
+ # supported icon fonts include: FontAwesome,
25
+ # Zurb Foundicons, and GitHub Octicons.
26
+ #
27
+ # = Icon Keys
28
+ #
29
+ # Icon keys must be supplied to most +Prawn::Icon+
30
+ # methods. Keys map directly to a unicode character
31
+ # within the font that produces a given icon. As a
32
+ # rule, included icon keys should match the keys from
33
+ # the font provider. The icon key mapping is specified
34
+ # in the font's +legend_file+, which is a +YAML+ file
35
+ # located in Prawn::Icon::FONTDIR/font/font.yml.
36
+ #
37
+ # Prawn::Icon::
38
+ # Houses the methods and interfaces necessary for
39
+ # rendering icons to the Prawn::Document.
40
+ #
41
+ # Prawn::Icon::FontData::
42
+ # Used to store various information about an icon font,
43
+ # including the key-to-unicode mapping information.
44
+ # Also houses methods to cache and lazily load the
45
+ # requested font data on a document basis.
46
+ #
47
+ # Prawn::Icon::Parser::
48
+ # Used to initially parse icons that are used with the
49
+ # inline_format: true option. The input string is parsed
50
+ # once for <icon></icon> tags, then the output is provided
51
+ # to Prawn's internal formatted text parser.
52
+ #
53
+ class Icon
54
+ FONTDIR = File.join \
55
+ File.expand_path('../../..', __FILE__), 'fonts'
56
+
57
+ module Interface
58
+ # Set up and draw an icon on this document. This
59
+ # method operates much like +Prawn::Text::Box+.
60
+ #
61
+ # == Parameters:
62
+ # key::
63
+ # Contains the key to a particular icon within
64
+ # a font family. If :inline_format is true,
65
+ # then key may contain formatted text marked
66
+ # with <icon></icon> tags and any tag supported
67
+ # by Prawn's parser.
68
+ #
69
+ # opts::
70
+ # A hash of options that may be supplied to
71
+ # the underlying +text+ method call.
72
+ #
73
+ # == Examples:
74
+ # pdf.icon 'fa-beer'
75
+ # pdf.icon '<icon color="0099FF">fa-arrows</icon>',
76
+ # inline_format: true
77
+ #
78
+ def icon(key, opts = {})
79
+ i = make_icon(key, opts)
80
+ i.render
81
+ i
82
+ end
83
+
84
+ # Initialize a new icon object, but do
85
+ # not render it to the document.
86
+ #
87
+ # == Parameters:
88
+ # key::
89
+ # Contains the key to a particular icon within
90
+ # a font family. If :inline_format is true,
91
+ # then key may contain formatted text marked
92
+ # with <icon></icon> tags and any tag supported
93
+ # by Prawn's parser.
94
+ #
95
+ # opts::
96
+ # A hash of options that may be supplied to
97
+ # the underlying text method call.
98
+ #
99
+ def make_icon(key, opts = {})
100
+ if opts[:inline_format]
101
+ inline_icon(key, opts)
102
+ else
103
+ Icon.new(key, self, opts)
104
+ end
105
+ end
106
+
107
+ # Initialize a new formatted text box containing
108
+ # icon information, but don't render it to the
109
+ # document.
110
+ #
111
+ # == Parameters:
112
+ # text::
113
+ # Input text to be parsed initially for <icon>
114
+ # tags, then passed to Prawn's formatted text
115
+ # parser.
116
+ #
117
+ # opts::
118
+ # A hash of options that may be supplied to the
119
+ # underlying text call.
120
+ #
121
+ def inline_icon(text, opts = {})
122
+ parsed = Icon::Parser.format(self, text)
123
+ content = Text::Formatted::Parser.format(parsed)
124
+ opts.merge!(inline_format: true, document: self)
125
+ Text::Formatted::Box.new(content, opts)
126
+ end
127
+ end
128
+
129
+ attr_reader :set, :unicode
130
+
131
+ def initialize(key, document, opts = {})
132
+ @pdf = document
133
+ @set = opts[:set] ||
134
+ FontData.specifier_from_key(key)
135
+ @data = FontData.load(document, @set)
136
+ @key = strip_specifier_from_key(key)
137
+ @unicode = @data.unicode(@key)
138
+ @options = opts
139
+ end
140
+
141
+ def render
142
+ @pdf.font(@data.path) do
143
+ @pdf.text @unicode, @options
144
+ end
145
+ end
146
+
147
+ private
148
+
149
+ def strip_specifier_from_key(key)
150
+ reg = Regexp.new "#{@data.specifier}-"
151
+ key.sub(reg, '') # Only one specifier
152
+ end
153
+ end
154
+ end
155
+
156
+ Prawn::Document.extensions << Prawn::Icon::Interface
@@ -0,0 +1,106 @@
1
+ # encoding: utf-8
2
+ #
3
+ # font_data.rb: Icon font metadata container/cache.
4
+ #
5
+ # Copyright October 2014, Jesse Doyle. All rights reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+
9
+ require 'yaml'
10
+
11
+ module Prawn
12
+ class Icon
13
+ class FontData
14
+ class << self
15
+ # Make a simple cache that contains font
16
+ # data that has been lazily loaded.
17
+ def load(document, set)
18
+ set = set.to_sym
19
+ @data ||= {}
20
+ @data[set] ||= FontData.new(document, set: set)
21
+ end
22
+
23
+ # Release all font references if requested.
24
+ def release_data
25
+ @data = {}
26
+ end
27
+
28
+ def unicode_from_key(document, key)
29
+ set = specifier_from_key(key)
30
+ key = key.sub(Regexp.new("#{set}-"), '')
31
+ load(document, set).unicode(key)
32
+ end
33
+
34
+ def specifier_from_key(key)
35
+ if key.nil? || key == ''
36
+ raise Prawn::Errors::IconKeyEmpty,
37
+ 'Icon key provided was nil.'
38
+ end
39
+
40
+ key.split('-')[0].to_sym
41
+ end
42
+ end
43
+
44
+ attr_reader :set
45
+
46
+ def initialize(document, opts = {})
47
+ @pdf = document
48
+ @set = opts[:set] || :fa
49
+ update_document_fonts!
50
+ end
51
+
52
+ def font_version
53
+ yaml[specifier]['__font_version__']
54
+ end
55
+
56
+ def legend_path
57
+ File.join(File.dirname(path), "#{@set}.yml")
58
+ end
59
+
60
+ def path
61
+ ttf = File.join(Icon::FONTDIR, @set.to_s, '*.ttf')
62
+ fonts = Dir[ttf]
63
+
64
+ if fonts.empty?
65
+ raise Prawn::Errors::UnknownFont,
66
+ "Icon font not found for set: #{@set}"
67
+ end
68
+
69
+ @path ||= fonts.first
70
+ end
71
+
72
+ def specifier
73
+ @specifier ||= yaml.keys[0]
74
+ end
75
+
76
+ def unicode(key)
77
+ char = yaml[specifier][key]
78
+
79
+ unless char
80
+ raise Prawn::Errors::IconNotFound,
81
+ "Key: #{specifier}-#{key} not found"
82
+ end
83
+
84
+ char
85
+ end
86
+
87
+ def keys
88
+ # Strip the first element: __font_version__
89
+ yaml[specifier].keys.map { |k| "#{specifier}-#{k}" }[1..-1]
90
+ end
91
+
92
+ def yaml
93
+ @yaml ||= YAML.load_file legend_path
94
+ end
95
+
96
+ private
97
+
98
+ def update_document_fonts!
99
+ @pdf.font_families.update(
100
+ @set.to_s => {
101
+ normal: path
102
+ })
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,126 @@
1
+ # encoding: utf-8
2
+ #
3
+ # parser.rb: Prawn icon tag text parser (pseudo-HTML).
4
+ #
5
+ # Copyright October 2014, Jesse Doyle. All rights reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+
9
+ module Prawn
10
+ class Icon
11
+ # Provides the necessary methods to enable the parsing
12
+ # of <icon> tags from input text.
13
+ #
14
+ # = Supported Tags:
15
+ # <icon></icon>::
16
+ # Place an icon key between the tags and the output
17
+ # will be translated into: <font name="fa">unicode</font>.
18
+ #
19
+ # = Supported Attributes:
20
+ #
21
+ # Various attributes will be extracted from +<icon>+ tags:
22
+ #
23
+ # color::
24
+ # The hex representation of a color that the icon should
25
+ # be rendered as. If left nil, the document's fill color
26
+ # will be used.
27
+ #
28
+ # size::
29
+ # The font size of a particular icon. If left nil, the
30
+ # document's font size will be used.
31
+ #
32
+ class Parser
33
+ PARSER_REGEX = Regexp.new \
34
+ '<icon[^>]*>|</icon>',
35
+ Regexp::IGNORECASE |
36
+ Regexp::MULTILINE
37
+
38
+ CONTENT_REGEX = /<icon[^>]*>(?<content>[^<]*)<\/icon>/mi
39
+
40
+ TAG_REGEX = /<icon[^>]*>[^<]*<\/icon>/mi
41
+
42
+ class << self
43
+ def format(document, string)
44
+ tokens = string.scan(PARSER_REGEX)
45
+ config = config_from_tokens(tokens)
46
+ content = string.scan(CONTENT_REGEX).flatten
47
+ icons = keys_to_unicode(document, content, config)
48
+ tags = icon_tags(icons)
49
+
50
+ string.gsub(TAG_REGEX).with_index do |_, i|
51
+ tags[i]
52
+ end
53
+ end
54
+
55
+ def config_from_tokens(tokens)
56
+ array = []
57
+
58
+ tokens.each do |token|
59
+ # Skip the closing tag
60
+ next if token =~ /<\/icon>/i
61
+
62
+ icon = {}
63
+
64
+ # TODO: Only support double quotes?
65
+ size = /size="([^"]*)"/i.match(token)
66
+ color = /color="([^"]*)"/i.match(token)
67
+
68
+ icon[:size] = size[1].to_f if size
69
+ icon[:color] = color[1] if color
70
+
71
+ array << icon
72
+ end
73
+
74
+ array
75
+ end
76
+
77
+ def icon_tags(icons)
78
+ tags = []
79
+
80
+ icons.each do |icon|
81
+ # Mandatory
82
+ content = icon[:content]
83
+ set = icon[:set]
84
+
85
+ # Optional
86
+ color = icon[:color]
87
+ size = icon[:size]
88
+
89
+ opening = "<font name=\"#{set}\""
90
+
91
+ unless color || size
92
+ tags << "#{opening}>#{content}</font>"
93
+ next
94
+ end
95
+
96
+ opening += " size=\"#{size}\"" if size
97
+ content = "<color rgb=\"#{color}\">#{content}</color>" if color
98
+
99
+ opening += '>'
100
+ tags << "#{opening}#{content}</font>"
101
+ end
102
+
103
+ tags
104
+ end
105
+
106
+ def keys_to_unicode(document, content, config)
107
+ icons = []
108
+
109
+ content.each_with_index do |icon, index|
110
+ options ||= {}
111
+ options = config[index] if config.any?
112
+ info = {
113
+ set: FontData.specifier_from_key(icon),
114
+ size: options[:size],
115
+ color: options[:color],
116
+ content: FontData.unicode_from_key(document, icon)
117
+ }
118
+ icons << info
119
+ end
120
+
121
+ icons
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+ #
3
+ # version.rb: Prawn icon versioning.
4
+ #
5
+ # Copyright October 2014, Jesse Doyle. All rights reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+
9
+ module Prawn
10
+ class Icon
11
+ VERSION = '0.5.0'.freeze
12
+ end
13
+ end
@@ -0,0 +1,36 @@
1
+ basedir = File.expand_path(File.dirname(__FILE__))
2
+ require "#{basedir}/lib/prawn/icon/version"
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = 'prawn-icon'
6
+ spec.version = Prawn::Icon::VERSION
7
+ spec.platform = Gem::Platform::RUBY
8
+ spec.summary = 'Provides icon fonts for PrawnPDF'
9
+ spec.files = Dir.glob('{lib,spec,fonts,examples}/**/**/*') +
10
+ ['prawn-icon.gemspec', 'Gemfile', 'Rakefile',
11
+ 'COPYING', 'LICENSE', 'GPLv2', 'GPLv3']
12
+ spec.require_path = 'lib'
13
+ spec.required_ruby_version = '>= 1.9.3'
14
+ spec.required_rubygems_version = '>= 1.3.6'
15
+
16
+ spec.homepage = "https://github.com/jessedoyle/prawn-icon/"
17
+
18
+ spec.test_files = Dir['spec/*_spec.rb']
19
+ spec.authors = ['Jesse Doyle']
20
+ spec.email = ['jdoyle@ualberta.ca']
21
+ spec.licenses = ['RUBY', 'GPL-2', 'GPL-3']
22
+
23
+ spec.add_development_dependency('prawn', '~> 1.3')
24
+ spec.add_development_dependency('pdf-inspector', '~> 1.1.0')
25
+ spec.add_development_dependency('rspec', '2.14.1')
26
+ spec.add_development_dependency('mocha')
27
+ spec.add_development_dependency('rake')
28
+ spec.add_development_dependency('simplecov')
29
+ spec.add_development_dependency('pdf-reader', '~>1.2')
30
+
31
+ spec.description = <<-END_DESC
32
+ Prawn::Icon provides various icon fonts including
33
+ FontAwesome, Foundation Icons and GitHub Octicons
34
+ for use with the Prawn PDF toolkit.
35
+ END_DESC
36
+ end
@@ -0,0 +1,93 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Copyright October 2014, Jesse Doyle. All rights reserved.
4
+ #
5
+ # This is free software. Please see the LICENSE and COPYING files for details.
6
+
7
+ require 'spec_helper'
8
+
9
+ describe Prawn::Icon::Interface do
10
+ describe '::icon' do
11
+ context 'valid icon key' do
12
+ context 'with options' do
13
+ it 'should handle text options (size)' do
14
+ pdf = create_pdf
15
+ pdf.icon 'fa-arrows', size: 60
16
+ text = PDF::Inspector::Text.analyze(pdf.render)
17
+
18
+ expect(text.font_settings.first[:size]).to eq(60)
19
+ end
20
+ end
21
+
22
+ context 'inline_format: true' do
23
+ it 'should handle text options (size)' do
24
+ pdf = create_pdf
25
+ # We need to flush the font cache here...
26
+ Prawn::Icon::FontData.release_data
27
+ pdf.icon '<icon size="60">fa-arrows</icon>', inline_format: true
28
+ text = PDF::Inspector::Text.analyze(pdf.render)
29
+
30
+ expect(text.strings.first).to eq("\uf047")
31
+ expect(text.font_settings.first[:size]).to eq(60.0)
32
+ end
33
+ end
34
+
35
+ context 'without options' do
36
+ it 'should render an icon to document' do
37
+ pdf = create_pdf
38
+ pdf.icon 'fa-arrows'
39
+ text = PDF::Inspector::Text.analyze(pdf.render)
40
+
41
+ expect(text.strings.first).to eq("\uf047")
42
+ end
43
+ end
44
+ end
45
+
46
+ context 'invalid icon key' do
47
+ it 'should raise IconNotFound' do
48
+ pdf = create_pdf
49
+ proc = Proc.new { pdf.icon 'fa-__INVALID' }
50
+
51
+ expect(proc).to raise_error(Prawn::Errors::IconNotFound)
52
+ end
53
+ end
54
+
55
+ context 'invalid specifier' do
56
+ it 'should raise UnknownFont' do
57
+ pdf = create_pdf
58
+ proc = Proc.new { pdf.icon '__INVALID__' }
59
+
60
+ expect(proc).to raise_error(Prawn::Errors::UnknownFont)
61
+ end
62
+ end
63
+ end
64
+
65
+ describe '::make_icon' do
66
+ context ':inline_format => false (default)' do
67
+ it 'should return a Prawn::Icon instance' do
68
+ pdf = create_pdf
69
+ icon = pdf.make_icon 'fa-arrows'
70
+
71
+ expect(icon.class).to eq(Prawn::Icon)
72
+ end
73
+ end
74
+
75
+ context ':inline_format => true' do
76
+ it 'should return a Prawn::::Text::Formatted::Box instance' do
77
+ pdf = create_pdf
78
+ icon = pdf.make_icon '<icon>fa-arrows</icon>', inline_format: true
79
+
80
+ expect(icon.class).to eq(Prawn::Text::Formatted::Box)
81
+ end
82
+ end
83
+ end
84
+
85
+ describe '::inline_icon' do
86
+ it 'should return a Prawn::Text::Formatted::Box instance' do
87
+ pdf = create_pdf
88
+ icon = pdf.inline_icon '<icon>fa-arrows</icon>'
89
+
90
+ expect(icon.class).to eq(Prawn::Text::Formatted::Box)
91
+ end
92
+ end
93
+ end