svg_drawer 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []