svg_drawer 1.0.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.
@@ -0,0 +1,153 @@
1
+ module SvgDrawer
2
+ class TextBox < Base
3
+ defaults font: 'Courier New',
4
+ font_style: [],
5
+ font_weight: 400,
6
+ font_size: 12,
7
+ font_color: 'black',
8
+ text_align: 'left',
9
+ text_valign: 'bottom',
10
+ line_height: 1,
11
+ wrap_policy: 'normal',
12
+ word_pattern: /[[:word:]]+[^[:word:]]\s?(?![^[:word:]])|[[:word:]]+|[^[:word:]]/,
13
+ overflow: false,
14
+ truncate: false,
15
+ truncate_with: '...',
16
+ text_padding: { top: 0, bottom: 0, left: 0, right: 0 }
17
+
18
+ def initialize(text, params = {})
19
+ super(params)
20
+ @text = text.to_s.strip.gsub(/\s+/, ' ')
21
+ fonts_config = SvgDrawer.configuration
22
+ @config = fonts_config['default'].merge(fonts_config[param(:font)] || {})
23
+ end
24
+
25
+ def width
26
+ return @width if @width
27
+ ensure_complete!
28
+ @width = param(:width, calculate_width).to_d
29
+ end
30
+
31
+ # If overflow is true, but we don't have any explicit height set,
32
+ # report the calculated height.
33
+ def height
34
+ return @height if @height
35
+ ensure_complete!
36
+ @height = param(:overflow) ?
37
+ param(:height, calculate_height).to_d :
38
+ [param(:height, 0), calculate_height].max.to_d
39
+ end
40
+
41
+ def incomplete
42
+ false
43
+ end
44
+
45
+ private
46
+
47
+ def _draw(parent)
48
+ # need symbol keys due to a bug in Rasem::SVGTag#write_styles
49
+ style = {}
50
+ style[:fill] = param(:font_color)
51
+ style[:'font-family'] = param(:font)
52
+ style[:'text-anchor'] = align_to_anchor(param(:text_align))
53
+ style[:'font-weight'] = param(:font_style).include?('bold') ? 'bold' : param(:font_weight)
54
+ style[:'font-style'] = 'italic' if param(:font_style).include?('italic')
55
+ style[:'font-size'] = param(:font_size).is_a?(Numeric) ?
56
+ "#{param(:font_size)}pt" :
57
+ param(:font_size)
58
+
59
+ svg_params = { x: 0, y: 0, style: style }
60
+
61
+ case param(:text_align)
62
+ when 'left' then svg_params[:x] += param(:text_padding)[:left]
63
+ when 'right' then svg_params[:x] += width - param(:text_padding)[:right]
64
+ when 'center' then svg_params[:x] += width.to_d / 2
65
+ end
66
+
67
+ y_deltas = calculate_y_deltas
68
+
69
+ Utils::RasemWrapper.group(parent, class: param(:class)) do |text_box_group|
70
+ root_y = svg_params[:y]
71
+
72
+ draw_border(text_box_group)
73
+ lines.zip(y_deltas).each do |line, delta_y|
74
+ svg_params[:y] = root_y + delta_y
75
+ escaped = line.encode(xml: :text)
76
+ Utils::RasemWrapper.text(text_box_group, svg_params) { |txt| txt.raw(escaped) }
77
+ end
78
+ end
79
+ end
80
+
81
+ def align_to_anchor(align)
82
+ case align
83
+ when 'left' then 'start'
84
+ when 'right' then 'end'
85
+ when 'center' then 'middle'
86
+ else raise "Bad text_align: #{align}. Valid are: [left, right, center]"
87
+ end
88
+ end
89
+
90
+ def font_width_px
91
+ @config['width']
92
+ end
93
+
94
+ def font_height_px
95
+ @config['height']
96
+ end
97
+
98
+ def calculate_line_height_px
99
+ font_height_px * param(:font_size) * param(:line_height)
100
+ end
101
+
102
+ def calculate_y_offset_px
103
+ font_height_px * param(:font_size) * @config['y_offset']
104
+ end
105
+
106
+ def lines
107
+ return [@text] if param(:overflow)
108
+ txt = param(:truncate) ?
109
+ Utils::Text.truncate(@text, chars_per_line, param(:truncate_with)) :
110
+ @text
111
+
112
+ Utils::Text.word_wrap(txt, chars_per_line, param(:word_pattern))
113
+ # Text.word_wrap(txt, chars_per_line, /\b/)
114
+ end
115
+
116
+ def chars_per_line
117
+ font_width = font_width_px.to_d * param(:font_size)
118
+ font_wrap_policy = @config['wrap_policies']
119
+ wrap_coeff = font_wrap_policy[param(:wrap_policy)]
120
+ adj_font_width = font_width * wrap_coeff
121
+ xpad = param(:text_padding)[:left] + param(:text_padding)[:right]
122
+ ((width - xpad) / adj_font_width).to_i
123
+ end
124
+
125
+ def calculate_width
126
+ font_width = font_width_px * param(:font_size)
127
+ @text.length * font_width
128
+ end
129
+
130
+ def calculate_height
131
+ return 0 if @text.empty?
132
+ ypad = param(:text_padding)[:top] + param(:text_padding)[:bottom]
133
+ ypad + lines.size * calculate_line_height_px
134
+ end
135
+
136
+ def calculate_y_deltas
137
+ lheight = calculate_line_height_px
138
+ yoffset = calculate_y_offset_px
139
+ base_deltas = 1.upto(lines.size).map { |lineno| lineno * lheight - yoffset }
140
+ remaining_height = height - calculate_height
141
+
142
+ case param(:text_valign)
143
+ when 'top'
144
+ base_deltas.map { |bd| bd + param(:text_padding)[:top] }
145
+ when 'bottom'
146
+ base_deltas.map { |bd| bd + remaining_height }
147
+ when 'middle'
148
+ base_deltas.map { |bd| bd + remaining_height / 2 }
149
+ else raise "Bad text_valign: #{param(:text_valign)}. Valid are: [top, bottom, middle]"
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,37 @@
1
+ module SvgDrawer
2
+ module Utils
3
+ class ParameterMerger
4
+ ParameterNotFound = Class.new(StandardError)
5
+
6
+ def initialize(*sources)
7
+ @sources = sources
8
+ end
9
+
10
+ # At least one of the sources must contain some value for the param
11
+ # (even a nil value)
12
+ def param(name)
13
+ source = @sources.find { |s| s.key?(name) }
14
+ raise ParameterNotFound, "No such param: #{name}" unless source
15
+ val = source[name]
16
+
17
+ val.is_a?(Hash) ? default_hash(name, val) : val
18
+ end
19
+
20
+ def param?(name)
21
+ !!param(name)
22
+ rescue ParameterNotFound
23
+ false
24
+ end
25
+
26
+ private
27
+
28
+ # When the value is the hash, populate its missing keys (if any)
29
+ # with default values
30
+ def default_hash(name, value)
31
+ @sources.reduce(value) do |hash, source|
32
+ source[name] ? source[name].merge(hash) : hash
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,14 @@
1
+ module SvgDrawer
2
+ module Utils
3
+ module RasemWrapper
4
+ module_function
5
+
6
+ %i[group rect text].each do |n|
7
+ define_method(n) do |svg_ctx, params = {}, &b|
8
+ compact_params = params.reject { |_k, v| v.nil? }
9
+ svg_ctx.public_send(n, compact_params) { b.call(self) if b }
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,48 @@
1
+ module SvgDrawer
2
+ module Utils
3
+ module Text
4
+ module_function
5
+
6
+ DEFAULT_WORD_PATTERN = /
7
+ [[:word:]]+ # chars
8
+ [^[:word:]] # delimiter
9
+ \s? # optional space
10
+ (?![^[:word:]]) # not followed by delimiter
11
+ |
12
+ [[:word:]]+ # chars
13
+ |
14
+ [^[:word:]] # delimiter
15
+ /x
16
+
17
+ def truncate(text, maxlen, trailer = '...')
18
+ return text unless text.length > maxlen
19
+ raise if maxlen < trailer.length
20
+ text.slice(0, maxlen - trailer.length).concat(trailer)
21
+ end
22
+
23
+ #
24
+ # Wrap on any word delimiter, but consider a word followed
25
+ # by a single delimiter and a space as unwrappable (e.g. end of sentence)
26
+ #
27
+ # If word itself is longer than max line length, force-wrap it
28
+ # on any char (effectively slicing it at maxlen)
29
+ #
30
+ def word_wrap(txt, maxlen, word_pattern = DEFAULT_WORD_PATTERN)
31
+ words = txt.scan(word_pattern)
32
+
33
+ words.each_with_object(['']) do |word, lines|
34
+ last = lines.last
35
+ wordlen = word.rstrip.length
36
+
37
+ if wordlen > maxlen
38
+ lines.concat(word.scan(/.{1,#{maxlen}}/))
39
+ elsif last != '' && last.length + wordlen > maxlen
40
+ lines << word
41
+ else
42
+ lines.last << word
43
+ end
44
+ end.delete_if(&:empty?).each(&:strip!)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,3 @@
1
+ module SvgDrawer
2
+ VERSION = '1.0.0'
3
+ end
data/lib/svg_drawer.rb ADDED
@@ -0,0 +1,31 @@
1
+ require 'bigdecimal'
2
+ require 'bigdecimal/util'
3
+ require 'rasem'
4
+ require 'yaml'
5
+
6
+ module SvgDrawer
7
+ class << self
8
+ attr_reader :configuration
9
+ end
10
+
11
+ config_file = File.join(File.dirname(__FILE__), 'fonts.yml')
12
+ @configuration = YAML.load_file(config_file)
13
+ end
14
+
15
+ require 'svg_drawer/version'
16
+ require 'svg_drawer/utils/text'
17
+ require 'svg_drawer/utils/parameter_merger'
18
+ require 'svg_drawer/utils/rasem_wrapper'
19
+ require 'svg_drawer/base'
20
+ require 'svg_drawer/text_box'
21
+ require 'svg_drawer/polyline'
22
+ require 'svg_drawer/multipolyline'
23
+ require 'svg_drawer/line'
24
+ require 'svg_drawer/path'
25
+ require 'svg_drawer/circle'
26
+ require 'svg_drawer/table/cell'
27
+ require 'svg_drawer/table/blank_row'
28
+ require 'svg_drawer/table/row'
29
+ require 'svg_drawer/table/table'
30
+ require 'svg_drawer/table/border'
31
+
@@ -0,0 +1,21 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "svg_drawer/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'svg_drawer'
8
+ spec.version = SvgDrawer::VERSION
9
+ spec.authors = ['Simeon Manolov']
10
+ spec.email = ['s.manolloff@gmail.com']
11
+ spec.summary = 'A ruby gem for building table-based SVG layouts'
12
+ spec.homepage = 'https://github.com/smanolloff/svg_drawer'
13
+ spec.license = 'MIT'
14
+
15
+ spec.require_paths = ['lib']
16
+ spec.files = Dir.glob('lib/**/*') + %w[LICENSE.txt svg_drawer.gemspec]
17
+
18
+ spec.add_dependency 'rasem', '~> 0.7'
19
+ spec.add_development_dependency 'bundler', '~> 1.15'
20
+ spec.add_development_dependency 'rspec', '~> 3.0'
21
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: svg_drawer
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Simeon Manolov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-11-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rasem
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.15'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.15'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description:
56
+ email:
57
+ - s.manolloff@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - LICENSE.txt
63
+ - lib/fonts.yml
64
+ - lib/svg_drawer.rb
65
+ - lib/svg_drawer/base.rb
66
+ - lib/svg_drawer/circle.rb
67
+ - lib/svg_drawer/line.rb
68
+ - lib/svg_drawer/multipolyline.rb
69
+ - lib/svg_drawer/path.rb
70
+ - lib/svg_drawer/polyline.rb
71
+ - lib/svg_drawer/table/blank_row.rb
72
+ - lib/svg_drawer/table/border.rb
73
+ - lib/svg_drawer/table/cell.rb
74
+ - lib/svg_drawer/table/row.rb
75
+ - lib/svg_drawer/table/table.rb
76
+ - lib/svg_drawer/text_box.rb
77
+ - lib/svg_drawer/utils/parameter_merger.rb
78
+ - lib/svg_drawer/utils/rasem_wrapper.rb
79
+ - lib/svg_drawer/utils/text.rb
80
+ - lib/svg_drawer/version.rb
81
+ - svg_drawer.gemspec
82
+ homepage: https://github.com/smanolloff/svg_drawer
83
+ licenses:
84
+ - MIT
85
+ metadata: {}
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ requirements: []
101
+ rubyforge_project:
102
+ rubygems_version: 2.4.3
103
+ signing_key:
104
+ specification_version: 4
105
+ summary: A ruby gem for building table-based SVG layouts
106
+ test_files: []