magic_cloud 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +21 -16
- data/lib/magic_cloud/bit_matrix.rb +4 -3
- data/lib/magic_cloud/canvas.rb +18 -9
- data/lib/magic_cloud/cloud.rb +48 -36
- data/lib/magic_cloud/collision_board.rb +7 -4
- data/lib/magic_cloud/debug.rb +5 -4
- data/lib/magic_cloud/layouter/place.rb +5 -6
- data/lib/magic_cloud/layouter.rb +6 -5
- data/lib/magic_cloud/palettes.rb +6 -5
- data/lib/magic_cloud/rect.rb +8 -6
- data/lib/magic_cloud/shape.rb +2 -1
- data/lib/magic_cloud/spriter.rb +7 -6
- data/lib/magic_cloud/version.rb +2 -2
- data/lib/magic_cloud/word.rb +3 -2
- data/lib/magic_cloud.rb +1 -1
- data/magic_cloud.gemspec +2 -0
- metadata +31 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3f49646844ea8dff390b90306511424e14e24fb898a1c7ea3a48e1d0f282da62
|
4
|
+
data.tar.gz: b1e962c6fd8bb07cf7be250f24b189441976b881fe9393091be172ec8d670678
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9e79e0950a3f0a8c72b5ba8d8cdaa22dedc825f50a269a286bd7245635bf749ca090f843e3bf64f3634e16a1fd9164b8651f1e8dc400a874d3d7b28684f56972
|
7
|
+
data.tar.gz: 24e3cb62f96a6107cd5ff01c23f963db0427449bc68458d839778a98875bb954e53b81c43917aa83ca1a25bc58b6837f2fe31daf3164bdb6ff248c5218220a3d
|
data/README.md
CHANGED
@@ -12,12 +12,16 @@ Usage
|
|
12
12
|
|
13
13
|
```ruby
|
14
14
|
words = [
|
15
|
-
[test, 50],
|
16
|
-
[me, 40],
|
17
|
-
[tenderly, 30],
|
15
|
+
['test', 50],
|
16
|
+
['me', 40],
|
17
|
+
['tenderly', 30],
|
18
18
|
# ....
|
19
19
|
]
|
20
20
|
cloud = MagicCloud::Cloud.new(words, rotate: :free, scale: :log)
|
21
|
+
|
22
|
+
# To save to file, if not redering it for a page
|
23
|
+
img = cloud.draw(960, 600) #default height/width
|
24
|
+
img.write('test.png')
|
21
25
|
```
|
22
26
|
|
23
27
|
Or from command-line:
|
@@ -61,7 +65,7 @@ References:
|
|
61
65
|
Performance
|
62
66
|
-----------
|
63
67
|
|
64
|
-
It's reasonable for me. On my small Thinkpad E330, some 50-words cloud
|
68
|
+
It's reasonable for me. On my small Thinkpad E330, some 50-words cloud
|
65
69
|
image, size 700×500, are typically generated in <3sec. It's not that cool,
|
66
70
|
yet not too long for you to fell asleep.
|
67
71
|
|
@@ -74,8 +78,8 @@ out than sparse Tahoma.
|
|
74
78
|
|
75
79
|
Major performance eater is perfect collision detection, which Wordle-like
|
76
80
|
cloud needs. MagicCloud for now uses really dumb algorithm with some
|
77
|
-
not-so-dumb optimizations. You can look into
|
78
|
-
`lib/magic_cloud/collision_board.rb` - everything can be optimized is
|
81
|
+
not-so-dumb optimizations. You can look into
|
82
|
+
`lib/magic_cloud/collision_board.rb` - everything can be optimized is
|
79
83
|
there; especially in `CollisionBoard#collides?` method.
|
80
84
|
|
81
85
|
I assume, for example, that naive rewriting of code in there as a C
|
@@ -88,18 +92,18 @@ criss-cross intersection check, and memoizing of last crossed sprite).
|
|
88
92
|
Memory effectiviness
|
89
93
|
--------------------
|
90
94
|
|
91
|
-
Basically: it's not.
|
95
|
+
Basically: it's not.
|
92
96
|
|
93
|
-
Plain Ruby arrays are used to represent collision bitmasks (each array
|
94
|
-
member stand for 1 bit), so, for example, 700×500 pixel cloud will requre
|
97
|
+
Plain Ruby arrays are used to represent collision bitmasks (each array
|
98
|
+
member stand for 1 bit), so, for example, 700×500 pixel cloud will requre
|
95
99
|
collision board size `700*500` (i.e. 350k array items only for board, and
|
96
100
|
slightly less for all sprites).
|
97
101
|
|
98
102
|
It should be wise to use some packing (considering each Ruby Fixmnum can
|
99
|
-
represent not one, but whole 32 bits). Unfortunately, all bit array
|
100
|
-
libraries I've tried are causing major slowdown of cloud computation.
|
101
|
-
With, say, 50 words we'll have literally millions of operation
|
102
|
-
`bitmask#[]` and `bitmask#[]=`, so, even methods
|
103
|
+
represent not one, but whole 32 bits). Unfortunately, all bit array
|
104
|
+
libraries I've tried are causing major slowdown of cloud computation.
|
105
|
+
With, say, 50 words we'll have literally millions of operation
|
106
|
+
`bitmask#[]` and `bitmask#[]=`, so, even methods
|
103
107
|
like `Fixnum#&` and `Fixnum#|` (typically used for bit array representation)
|
104
108
|
are causing significant overload.
|
105
109
|
|
@@ -111,10 +115,10 @@ cloud = MagicCloud.new(words, palette: palette, rotate: rotate)
|
|
111
115
|
```
|
112
116
|
|
113
117
|
* `:palette` (default is `:color20`):
|
114
|
-
* `:category10`, `:category20`, ... - from (
|
118
|
+
* `:category10`, `:category20`, ... - from [D3.js](https://github.com/d3/d3-scale/blob/master/README.md#category-scales)
|
115
119
|
* `[array, of, colors]` - each color should be hex color, or any other RMagick color string (See "Color names at http://www.imagemagick.org/RMagick/doc/imusage.html)
|
116
120
|
* any lambda, accepting `(word, index)` and returning color string
|
117
|
-
* any object, responding to `color(word, index)` - so, you can make color
|
121
|
+
* any object, responding to `color(word, index)` - so, you can make color
|
118
122
|
depend on tag text, not only on its number in tags list
|
119
123
|
* `:rotate` - rotation algorithm:
|
120
124
|
* `:square` (only horizontal and vertical words) - it's default
|
@@ -129,7 +133,8 @@ cloud = MagicCloud.new(words, palette: palette, rotate: rotate)
|
|
129
133
|
* `:linear` - linear scaling (default);
|
130
134
|
* `:log` - logarithmic scaling;
|
131
135
|
* `:sqrt` - square root scaling;
|
132
|
-
* `:font_family` (Impact is default).
|
136
|
+
* `:font_family` (Impact is default, ex. Arial, Helvetica, Futura).
|
137
|
+
* `:font` - Full path to custom font file. Overwrite `:font_family`.
|
133
138
|
|
134
139
|
Current state
|
135
140
|
-------------
|
@@ -1,4 +1,5 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module MagicCloud
|
3
4
|
# Dead simple 2-dimensional "bit matrix", storing 1s and 0s.
|
4
5
|
# Not memory effectife at all, but the fastest pure-Ruby solution
|
@@ -25,8 +26,8 @@ module MagicCloud
|
|
25
26
|
end
|
26
27
|
|
27
28
|
def dump
|
28
|
-
(0...height).map{|y|
|
29
|
-
(0...width).map{|x| at(x, y) ? ' ' : 'x'}.join
|
29
|
+
(0...height).map { |y|
|
30
|
+
(0...width).map { |x| at(x, y) ? ' ' : 'x' }.join
|
30
31
|
}.join("\n")
|
31
32
|
end
|
32
33
|
end
|
data/lib/magic_cloud/canvas.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rmagick'
|
3
4
|
|
4
5
|
module MagicCloud
|
5
6
|
# Thin wrapper around RMagick, incapsulating ALL the real drawing.
|
@@ -9,16 +10,18 @@ module MagicCloud
|
|
9
10
|
class Canvas
|
10
11
|
def initialize(w, h, back = 'transparent')
|
11
12
|
@width, @height = w, h
|
12
|
-
@internal = Magick::Image.new(w, h){|i| i.background_color =
|
13
|
+
@internal = Magick::Image.new(w, h) { |i| i.background_color = back }
|
13
14
|
end
|
14
15
|
|
15
16
|
attr_reader :internal, :width, :height
|
16
17
|
|
17
18
|
RADIANS = Math::PI / 180
|
18
19
|
|
19
|
-
def draw_text(text, options = {})
|
20
|
+
def draw_text(text, options = {}) # rubocop:todo Metrics/AbcSize
|
21
|
+
return nil if text.empty?
|
22
|
+
|
20
23
|
draw = Magick::Draw.new # FIXME: is it necessary every time?
|
21
|
-
|
24
|
+
|
22
25
|
x = options.fetch(:x, 0)
|
23
26
|
y = options.fetch(:y, 0)
|
24
27
|
rotate = options.fetch(:rotate, 0)
|
@@ -47,16 +50,16 @@ module MagicCloud
|
|
47
50
|
@internal.export_pixels(x, y, w, h, 'RGBA')
|
48
51
|
end
|
49
52
|
|
50
|
-
# rubocop:disable TrivialAccessors
|
51
53
|
def render
|
52
54
|
@internal
|
53
55
|
end
|
54
|
-
# rubocop:enable TrivialAccessors
|
55
56
|
|
56
57
|
private
|
57
58
|
|
58
59
|
def set_text_options(draw, options)
|
59
60
|
draw.font_family = options[:font_family]
|
61
|
+
draw.font = options[:font] if options[:font]
|
62
|
+
|
60
63
|
draw.font_weight = Magick::NormalWeight
|
61
64
|
draw.font_style = Magick::NormalStyle
|
62
65
|
|
@@ -67,13 +70,13 @@ module MagicCloud
|
|
67
70
|
end
|
68
71
|
|
69
72
|
def _measure_text(draw, text, rotate)
|
70
|
-
metrics = draw.get_type_metrics(
|
73
|
+
metrics = draw.get_type_metrics(%("#{validate_text(text)}m"))
|
71
74
|
w, h = rotated_metrics(metrics.width, metrics.height, rotate)
|
72
75
|
|
73
76
|
Rect.new(0, 0, w, h)
|
74
77
|
end
|
75
78
|
|
76
|
-
def rotated_metrics(w, h, degrees)
|
79
|
+
def rotated_metrics(w, h, degrees) # rubocop:todo Metrics/AbcSize
|
77
80
|
radians = degrees * Math::PI / 180
|
78
81
|
|
79
82
|
# FIXME: not too clear, just straightforward from d3.cloud
|
@@ -89,5 +92,11 @@ module MagicCloud
|
|
89
92
|
|
90
93
|
[w, h]
|
91
94
|
end
|
95
|
+
|
96
|
+
def validate_text(text)
|
97
|
+
return text +=' ' if text[-1] == '%'
|
98
|
+
|
99
|
+
text
|
100
|
+
end
|
92
101
|
end
|
93
102
|
end
|
data/lib/magic_cloud/cloud.rb
CHANGED
@@ -1,18 +1,19 @@
|
|
1
|
-
#
|
2
|
-
require_relative './rect'
|
3
|
-
require_relative './canvas'
|
4
|
-
require_relative './palettes'
|
1
|
+
# frozen_string_literal: true
|
5
2
|
|
6
|
-
require_relative '
|
3
|
+
require_relative 'rect'
|
4
|
+
require_relative 'canvas'
|
5
|
+
require_relative 'palettes'
|
7
6
|
|
8
|
-
require_relative '
|
9
|
-
require_relative './spriter'
|
7
|
+
require_relative 'word'
|
10
8
|
|
11
|
-
require_relative '
|
9
|
+
require_relative 'layouter'
|
10
|
+
require_relative 'spriter'
|
11
|
+
|
12
|
+
require_relative 'debug'
|
12
13
|
|
13
14
|
module MagicCloud
|
14
15
|
# Main word-cloud class. Takes words with sizes, returns image
|
15
|
-
class Cloud
|
16
|
+
class Cloud # rubocop:todo Metrics/ClassLength
|
16
17
|
def initialize(words, options = {})
|
17
18
|
@words = words.sort_by(&:last).reverse
|
18
19
|
@options = options
|
@@ -23,15 +24,21 @@ module MagicCloud
|
|
23
24
|
|
24
25
|
DEFAULT_FAMILY = 'Impact'
|
25
26
|
|
26
|
-
|
27
|
+
# rubocop:todo Metrics/MethodLength
|
28
|
+
def draw(width, height) # rubocop:todo Metrics/AbcSize, Metrics/MethodLength
|
27
29
|
# FIXME: do it in init, for specs would be happy
|
28
|
-
shapes = @words.each_with_index.map{|(word, size), i|
|
29
|
-
|
30
|
-
word,
|
30
|
+
shapes = @words.each_with_index.map { |(word, size), i|
|
31
|
+
word_options = {
|
31
32
|
font_family: @options[:font_family] || DEFAULT_FAMILY,
|
32
33
|
font_size: scaler.call(word, size, i),
|
33
34
|
color: palette.call(word, i),
|
34
35
|
rotate: rotator.call(word, i)
|
36
|
+
}
|
37
|
+
word_options[:font] = @options[:font] if @options[:font]
|
38
|
+
|
39
|
+
Word.new(
|
40
|
+
word,
|
41
|
+
word_options
|
35
42
|
)
|
36
43
|
}
|
37
44
|
|
@@ -44,10 +51,11 @@ module MagicCloud
|
|
44
51
|
visible = layouter.layout!(shapes)
|
45
52
|
|
46
53
|
canvas = Canvas.new(width, height, 'white')
|
47
|
-
visible.each{|sh| sh.draw(canvas)}
|
54
|
+
visible.each { |sh| sh.draw(canvas) }
|
48
55
|
|
49
56
|
canvas.render
|
50
57
|
end
|
58
|
+
# rubocop:enable Metrics/MethodLength
|
51
59
|
|
52
60
|
private
|
53
61
|
|
@@ -61,11 +69,11 @@ module MagicCloud
|
|
61
69
|
when Symbol
|
62
70
|
make_const_palette(source)
|
63
71
|
when Array
|
64
|
-
->(_, index){source[index % source.size]}
|
72
|
+
->(_, index) { source[index % source.size] }
|
65
73
|
when Proc
|
66
74
|
source
|
67
|
-
when ->(s){s.respond_to?(:color)}
|
68
|
-
->(word, index){source.color(word, index)}
|
75
|
+
when ->(s) { s.respond_to?(:color) }
|
76
|
+
->(word, index) { source.color(word, index) }
|
69
77
|
else
|
70
78
|
fail ArgumentError, "Unknown palette: #{source.inspect}"
|
71
79
|
end
|
@@ -75,29 +83,29 @@ module MagicCloud
|
|
75
83
|
palette = PALETTES[sym] or
|
76
84
|
fail(ArgumentError, "Unknown palette: #{sym.inspect}")
|
77
85
|
|
78
|
-
->(_, index){palette[index % palette.size]}
|
86
|
+
->(_, index) { palette[index % palette.size] }
|
79
87
|
end
|
80
88
|
|
81
89
|
def make_rotator(source)
|
82
90
|
case source
|
83
91
|
when :none
|
84
|
-
->(*){0}
|
92
|
+
->(*) { 0 }
|
85
93
|
when :square
|
86
|
-
->(*){
|
94
|
+
->(*) {
|
87
95
|
(rand * 2).to_i * 90
|
88
96
|
}
|
89
97
|
when :free
|
90
|
-
->(*){
|
98
|
+
->(*) {
|
91
99
|
(((rand * 6) - 3) * 30).round
|
92
100
|
}
|
93
101
|
when Array
|
94
|
-
->(*){
|
102
|
+
->(*) {
|
95
103
|
source.sample
|
96
104
|
}
|
97
105
|
when Proc
|
98
106
|
source
|
99
|
-
when ->(s){s.respond_to?(:rotate)}
|
100
|
-
->(word, index){source.rotate(word, index)}
|
107
|
+
when ->(s) { s.respond_to?(:rotate) }
|
108
|
+
->(word, index) { source.rotate(word, index) }
|
101
109
|
else
|
102
110
|
fail ArgumentError, "Unknown rotation algo: #{source.inspect}"
|
103
111
|
end
|
@@ -111,27 +119,31 @@ module MagicCloud
|
|
111
119
|
norm =
|
112
120
|
case algo
|
113
121
|
when :no
|
114
|
-
|
115
|
-
return ->(_word, size, _index){size}
|
122
|
+
return ->(_word, size, _index) { size }
|
116
123
|
when :linear
|
117
|
-
->(x){x}
|
124
|
+
->(x) { x }
|
118
125
|
when :log
|
119
|
-
->(x){Math.log(x) / Math.log(10)}
|
126
|
+
->(x) { Math.log(x) / Math.log(10) }
|
120
127
|
when :sqrt
|
121
|
-
->(x){Math.sqrt(x)}
|
128
|
+
->(x) { Math.sqrt(x) }
|
122
129
|
else
|
123
130
|
fail ArgumentError, "Unknown scaling algo: #{algo.inspect}"
|
124
131
|
end
|
125
|
-
|
132
|
+
|
126
133
|
smin = norm.call(words.map(&:last).min)
|
127
134
|
smax = norm.call(words.map(&:last).max)
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
(
|
133
|
-
|
135
|
+
|
136
|
+
if smin == smax
|
137
|
+
->(_word, _size, _index) { FONT_MIN } # Return the minimum font size if no scaling is needed
|
138
|
+
else
|
139
|
+
koeff = (FONT_MAX - FONT_MIN).to_f / (smax - smin)
|
140
|
+
->(_word, size, _index) {
|
141
|
+
ssize = norm.call(size)
|
142
|
+
((ssize - smin).to_f * koeff + FONT_MIN).to_i
|
143
|
+
}
|
144
|
+
end
|
134
145
|
end
|
146
|
+
|
135
147
|
# rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity,Metrics/AbcSize
|
136
148
|
end
|
137
149
|
end
|
@@ -1,5 +1,6 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'bit_matrix'
|
3
4
|
|
4
5
|
module MagicCloud
|
5
6
|
# Pixel-by-pixel collision board
|
@@ -17,7 +18,7 @@ module MagicCloud
|
|
17
18
|
attr_reader :rects, :intersections_cache
|
18
19
|
|
19
20
|
def criss_cross_collision?(rect)
|
20
|
-
if rects.any?{|r| r.criss_cross?(rect)}
|
21
|
+
if rects.any? { |r| r.criss_cross?(rect) }
|
21
22
|
Debug.stats[:criss_cross] += 1
|
22
23
|
true
|
23
24
|
else
|
@@ -25,6 +26,7 @@ module MagicCloud
|
|
25
26
|
end
|
26
27
|
end
|
27
28
|
|
29
|
+
# rubocop:disable Lint/HashCompareByIdentity -- probably should be followed, but don't have time to test it now
|
28
30
|
def collides_previous?(shape, intersections)
|
29
31
|
prev_idx = intersections_cache[shape.object_id]
|
30
32
|
|
@@ -52,6 +54,7 @@ module MagicCloud
|
|
52
54
|
|
53
55
|
false
|
54
56
|
end
|
57
|
+
# rubocop:enable Lint/HashCompareByIdentity
|
55
58
|
|
56
59
|
def collides?(shape)
|
57
60
|
Debug.stats[:collide_total] += 1
|
@@ -64,7 +67,7 @@ module MagicCloud
|
|
64
67
|
return true if criss_cross_collision?(shape.rect)
|
65
68
|
|
66
69
|
# then find which of placed sprites rectangles tag intersects
|
67
|
-
intersections = rects.map{|r| r.intersect(shape.rect)}
|
70
|
+
intersections = rects.map { |r| r.intersect(shape.rect) }
|
68
71
|
|
69
72
|
# no need to further check: this tag is not inside any others' rectangle
|
70
73
|
if intersections.compact.empty?
|
data/lib/magic_cloud/debug.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'forwardable'
|
3
4
|
require 'logger'
|
4
5
|
|
@@ -15,14 +16,14 @@ module MagicCloud
|
|
15
16
|
end
|
16
17
|
|
17
18
|
def initialize
|
18
|
-
@logger = Logger.new(
|
19
|
-
@stats = Hash.new{|h, k| h[k] = 0}
|
19
|
+
@logger = Logger.new($stdout).tap { |l| l.level = Logger::FATAL }
|
20
|
+
@stats = Hash.new { |h, k| h[k] = 0 }
|
20
21
|
end
|
21
22
|
|
22
23
|
attr_reader :logger, :stats
|
23
24
|
|
24
25
|
def reset!
|
25
|
-
@stats = Hash.new{|h, k| h[k] = 0}
|
26
|
+
@stats = Hash.new { |h, k| h[k] = 0 }
|
26
27
|
end
|
27
28
|
end
|
28
29
|
end
|
@@ -1,4 +1,5 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module MagicCloud
|
3
4
|
class Layouter
|
4
5
|
class PlaceNotFound < RuntimeError
|
@@ -9,7 +10,7 @@ module MagicCloud
|
|
9
10
|
# 2. at each step, shift in spiral from the previous place
|
10
11
|
# 3. always knows, if the place "ready" for shape (empty and inside board)
|
11
12
|
class Place
|
12
|
-
def initialize(layouter, shape)
|
13
|
+
def initialize(layouter, shape) # rubocop:todo Metrics/AbcSize
|
13
14
|
@layouter, @shape = layouter, shape
|
14
15
|
|
15
16
|
# initial position
|
@@ -60,10 +61,9 @@ module MagicCloud
|
|
60
61
|
rectangular_spiral(step)
|
61
62
|
end
|
62
63
|
|
63
|
-
# rubocop:disable Metrics/AbcSize
|
64
64
|
def archimedean_spiral(size)
|
65
65
|
e = width / height
|
66
|
-
->(t){
|
66
|
+
->(t) {
|
67
67
|
t1 = t * size * 0.01
|
68
68
|
|
69
69
|
[
|
@@ -78,7 +78,7 @@ module MagicCloud
|
|
78
78
|
dx = dy * @layouter.width / @layouter.height
|
79
79
|
x = 0
|
80
80
|
y = 0
|
81
|
-
->(t){
|
81
|
+
->(t) {
|
82
82
|
sign = t < 0 ? -1 : 1
|
83
83
|
|
84
84
|
# zverok: this is original comment & code from d3.layout.cloud.js
|
@@ -95,7 +95,6 @@ module MagicCloud
|
|
95
95
|
[x, y].map(&:round)
|
96
96
|
}
|
97
97
|
end
|
98
|
-
# rubocop:enable Metrics/AbcSize
|
99
98
|
end
|
100
99
|
end
|
101
100
|
end
|
data/lib/magic_cloud/layouter.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'collision_board'
|
3
4
|
|
4
5
|
module MagicCloud
|
5
6
|
# Main magic of magic cloud - layouting shapes without collisions.
|
@@ -49,7 +50,7 @@ module MagicCloud
|
|
49
50
|
|
50
51
|
board.add(shape)
|
51
52
|
Debug.logger.info 'Place for %p found in %i steps (%.2f sec)' %
|
52
|
-
|
53
|
+
[shape, steps, Time.now-start]
|
53
54
|
|
54
55
|
break
|
55
56
|
end
|
@@ -57,11 +58,11 @@ module MagicCloud
|
|
57
58
|
true
|
58
59
|
rescue PlaceNotFound
|
59
60
|
Debug.logger.warn 'No place for %p found in %i steps (%.2f sec)' %
|
60
|
-
|
61
|
+
[shape, steps, Time.now-start]
|
61
62
|
|
62
63
|
false
|
63
64
|
end
|
64
65
|
end
|
65
66
|
end
|
66
67
|
|
67
|
-
require_relative '
|
68
|
+
require_relative 'layouter/place'
|
data/lib/magic_cloud/palettes.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module MagicCloud
|
3
4
|
PALETTES = {
|
4
5
|
# Categorical colors from d3
|
5
|
-
# Source: https://github.com/
|
6
|
-
|
6
|
+
# Source: https://github.com/d3/d3-scale/blob/master/README.md#category-scales
|
7
|
+
|
7
8
|
category10: %w[
|
8
9
|
#1f77b4 #ff7f0e #2ca02c #d62728 #9467bd
|
9
10
|
#8c564b #e377c2 #7f7f7f #bcbd22 #17becf
|
@@ -15,7 +16,7 @@ module MagicCloud
|
|
15
16
|
#8c564b #c49c94 #e377c2 #f7b6d2 #7f7f7f
|
16
17
|
#c7c7c7 #bcbd22 #dbdb8d #17becf #9edae5
|
17
18
|
],
|
18
|
-
|
19
|
+
|
19
20
|
category20b: %w[
|
20
21
|
#393b79 #5254a3 #6b6ecf #9c9ede #637939
|
21
22
|
#8ca252 #b5cf6b #cedb9c #8c6d31 #bd9e39
|
@@ -29,5 +30,5 @@ module MagicCloud
|
|
29
30
|
#a1d99b #c7e9c0 #756bb1 #9e9ac8 #bcbddc
|
30
31
|
#dadaeb #636363 #969696 #bdbdbd #d9d9d9
|
31
32
|
]
|
32
|
-
}
|
33
|
+
}.freeze
|
33
34
|
end
|
data/lib/magic_cloud/rect.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module MagicCloud
|
3
4
|
# Utility geometrical rectangle, implementing arithmetic interactions
|
4
5
|
# with other rectangles
|
@@ -9,6 +10,7 @@ module MagicCloud
|
|
9
10
|
end
|
10
11
|
|
11
12
|
attr_accessor :x0, :y0, :x1, :y1
|
13
|
+
|
12
14
|
# NB: we are trying to use instance variables instead of accessors
|
13
15
|
# inside this class methods, because they are called so many
|
14
16
|
# times that accessor overhead IS significant.
|
@@ -36,20 +38,18 @@ module MagicCloud
|
|
36
38
|
@y0 = y
|
37
39
|
end
|
38
40
|
|
39
|
-
# rubocop:disable Metrics/AbcSize
|
40
41
|
def adjust!(other)
|
41
42
|
@x0 = other.x0 if other.x0 < @x0
|
42
43
|
@y0 = other.y0 if other.y0 < @y0
|
43
44
|
@x1 = other.x1 if other.x1 > @x1
|
44
45
|
@y1 = other.y1 if other.y1 > @y1
|
45
46
|
end
|
46
|
-
# rubocop:enable Metrics/AbcSize
|
47
47
|
|
48
48
|
def adjust(other)
|
49
|
-
dup.tap{|d| d.adjust!(other)}
|
49
|
+
dup.tap { |d| d.adjust!(other) }
|
50
50
|
end
|
51
51
|
|
52
|
-
# rubocop:disable Metrics/
|
52
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
53
53
|
def criss_cross?(other)
|
54
54
|
# case 1: this one is horizontal:
|
55
55
|
# overlaps other by x, to right and left, and goes inside it by y
|
@@ -60,15 +60,17 @@ module MagicCloud
|
|
60
60
|
@y0 < other.y0 && @y1 > other.y1 &&
|
61
61
|
@x0 > other.x0 && @x1 < other.x1
|
62
62
|
end
|
63
|
-
# rubocop:enable Metrics/
|
63
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
64
64
|
|
65
65
|
def intersect(other)
|
66
66
|
# direct comparison is dirtier, yet significantly faster than
|
67
67
|
# something like [@x0, other.x0].max
|
68
|
+
# rubocop:disable Style/MinMaxComparison
|
68
69
|
ix0 = @x0 > other.x0 ? @x0 : other.x0
|
69
70
|
ix1 = @x1 < other.x1 ? @x1 : other.x1
|
70
71
|
iy0 = @y0 > other.y0 ? @y0 : other.y0
|
71
72
|
iy1 = @y1 < other.y1 ? @y1 : other.y1
|
73
|
+
# rubocop:enable Style/MinMaxComparison
|
72
74
|
|
73
75
|
if ix0 > ix1 || iy0 > iy1
|
74
76
|
nil # rectangles are not intersected, in fact
|
data/lib/magic_cloud/shape.rb
CHANGED
data/lib/magic_cloud/spriter.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'bit_matrix'
|
3
4
|
|
4
5
|
module MagicCloud
|
5
6
|
# Incapsulates sprite maker for any Shape, able to draw itself.
|
@@ -21,14 +22,14 @@ module MagicCloud
|
|
21
22
|
end
|
22
23
|
|
23
24
|
Debug.logger.info 'Sprites ready: %i sec, %i canvases' %
|
24
|
-
|
25
|
+
[Time.now - start, Debug.stats[:canvases]]
|
25
26
|
end
|
26
27
|
|
27
28
|
private
|
28
29
|
|
29
30
|
attr_reader :canvas, :cur_x, :cur_y, :row_height
|
30
31
|
|
31
|
-
def make_sprite(shape)
|
32
|
+
def make_sprite(shape) # rubocop:todo Metrics/AbcSize
|
32
33
|
rect = shape.measure(canvas)
|
33
34
|
ensure_position(rect)
|
34
35
|
|
@@ -42,10 +43,10 @@ module MagicCloud
|
|
42
43
|
shift_position(rect)
|
43
44
|
|
44
45
|
Debug.logger.debug 'Sprite for %p ready: %i×%i' %
|
45
|
-
|
46
|
+
[shape, shape.sprite.width, shape.sprite.height]
|
46
47
|
end
|
47
48
|
|
48
|
-
CANVAS_SIZE = [1024, 1024]
|
49
|
+
CANVAS_SIZE = [1024, 1024].freeze
|
49
50
|
|
50
51
|
def restart_canvas!
|
51
52
|
Debug.stats[:canvases] += 1
|
data/lib/magic_cloud/version.rb
CHANGED
data/lib/magic_cloud/word.rb
CHANGED
data/lib/magic_cloud.rb
CHANGED
data/magic_cloud.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: magic_cloud
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Victor Shepelev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-09-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rmagick
|
@@ -80,6 +80,34 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubygems-tasks
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
83
111
|
description: |2
|
84
112
|
Simple, pure-ruby library for making pretty Wordle-like clouds.
|
85
113
|
It uses RMagick as graphic backend.
|
@@ -128,8 +156,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
128
156
|
- !ruby/object:Gem::Version
|
129
157
|
version: '0'
|
130
158
|
requirements: []
|
131
|
-
|
132
|
-
rubygems_version: 2.4.8
|
159
|
+
rubygems_version: 3.4.10
|
133
160
|
signing_key:
|
134
161
|
specification_version: 4
|
135
162
|
summary: Pretty word cloud maker for Ruby
|