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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/lib/fonts.yml +43 -0
- data/lib/svg_drawer/base.rb +215 -0
- data/lib/svg_drawer/circle.rb +139 -0
- data/lib/svg_drawer/line.rb +10 -0
- data/lib/svg_drawer/multipolyline.rb +144 -0
- data/lib/svg_drawer/path.rb +43 -0
- data/lib/svg_drawer/polyline.rb +171 -0
- data/lib/svg_drawer/table/blank_row.rb +32 -0
- data/lib/svg_drawer/table/border.rb +62 -0
- data/lib/svg_drawer/table/cell.rb +65 -0
- data/lib/svg_drawer/table/row.rb +127 -0
- data/lib/svg_drawer/table/table.rb +147 -0
- data/lib/svg_drawer/text_box.rb +153 -0
- data/lib/svg_drawer/utils/parameter_merger.rb +37 -0
- data/lib/svg_drawer/utils/rasem_wrapper.rb +14 -0
- data/lib/svg_drawer/utils/text.rb +48 -0
- data/lib/svg_drawer/version.rb +3 -0
- data/lib/svg_drawer.rb +31 -0
- data/svg_drawer.gemspec +21 -0
- metadata +106 -0
@@ -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
|
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
|
+
|
data/svg_drawer.gemspec
ADDED
@@ -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: []
|