ruby_wordcram 1.0.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.mvn/extensions.xml +8 -0
- data/.mvn/wrapper/maven-wrapper.properties +1 -0
- data/Rakefile +28 -5
- data/docs/_posts/2017-03-07-getting_started.md +3 -2
- data/docs/_posts/2017-03-07-under_the_hood.md +33 -0
- data/lib/WordCram.jar +0 -0
- data/lib/jsoup-1.10.2.jar +0 -0
- data/lib/ruby_wordcram/version.rb +1 -1
- data/lib/ruby_wordcram.rb +1 -2
- data/pom.rb +53 -0
- data/pom.xml +87 -0
- data/ruby_wordcram.gemspec +1 -2
- data/src/cue/lang/Counter.java +141 -0
- data/src/cue/lang/IterableText.java +10 -0
- data/src/cue/lang/NGramIterator.java +151 -0
- data/src/cue/lang/SentenceIterator.java +86 -0
- data/src/cue/lang/WordIterator.java +60 -0
- data/src/cue/lang/stop/StopWords.java +114 -0
- data/src/cue/lang/stop/arabic +351 -0
- data/src/cue/lang/stop/armenian +45 -0
- data/src/cue/lang/stop/catalan +219 -0
- data/src/cue/lang/stop/croatian +2024 -0
- data/src/cue/lang/stop/czech +256 -0
- data/src/cue/lang/stop/danish +94 -0
- data/src/cue/lang/stop/dutch +107 -0
- data/src/cue/lang/stop/english +183 -0
- data/src/cue/lang/stop/esperanto +180 -0
- data/src/cue/lang/stop/farsi +966 -0
- data/src/cue/lang/stop/finnish +235 -0
- data/src/cue/lang/stop/french +543 -0
- data/src/cue/lang/stop/german +231 -0
- data/src/cue/lang/stop/greek +637 -0
- data/src/cue/lang/stop/hebrew +220 -0
- data/src/cue/lang/stop/hindi +97 -0
- data/src/cue/lang/stop/hungarian +202 -0
- data/src/cue/lang/stop/italian +279 -0
- data/src/cue/lang/stop/latin +1 -0
- data/src/cue/lang/stop/norwegian +176 -0
- data/src/cue/lang/stop/polish +138 -0
- data/src/cue/lang/stop/portuguese +204 -0
- data/src/cue/lang/stop/romanian +284 -0
- data/src/cue/lang/stop/russian +652 -0
- data/src/cue/lang/stop/slovak +110 -0
- data/src/cue/lang/stop/slovenian +448 -0
- data/src/cue/lang/stop/spanish +308 -0
- data/src/cue/lang/stop/swedish +114 -0
- data/src/cue/lang/stop/turkish +117 -0
- data/src/cue/lang/unicode/BlockUtil.java +103 -0
- data/src/cue/lang/unicode/Normalizer.java +55 -0
- data/src/cue/lang/unicode/Normalizer6.java +32 -0
- data/src/license.txt +201 -0
- data/src/wordcram/Anglers.java +137 -0
- data/src/wordcram/BBTree.java +133 -0
- data/src/wordcram/BBTreeBuilder.java +61 -0
- data/src/wordcram/Colorers.java +52 -0
- data/src/wordcram/EngineWord.java +73 -0
- data/src/wordcram/Fonters.java +17 -0
- data/src/wordcram/HsbWordColorer.java +28 -0
- data/src/wordcram/ImageShaper.java +91 -0
- data/src/wordcram/Observer.java +9 -0
- data/src/wordcram/PlacerHeatMap.java +134 -0
- data/src/wordcram/Placers.java +74 -0
- data/src/wordcram/PlottingWordNudger.java +38 -0
- data/src/wordcram/PlottingWordPlacer.java +36 -0
- data/src/wordcram/ProcessingWordRenderer.java +42 -0
- data/src/wordcram/RandomWordNudger.java +44 -0
- data/src/wordcram/RenderOptions.java +10 -0
- data/src/wordcram/ShapeBasedPlacer.java +66 -0
- data/src/wordcram/Sizers.java +54 -0
- data/src/wordcram/SketchCallbackObserver.java +70 -0
- data/src/wordcram/SpiralWordNudger.java +31 -0
- data/src/wordcram/SvgWordRenderer.java +110 -0
- data/src/wordcram/SwirlWordPlacer.java +25 -0
- data/src/wordcram/UpperLeftWordPlacer.java +27 -0
- data/src/wordcram/WaveWordPlacer.java +25 -0
- data/src/wordcram/Word.java +357 -0
- data/src/wordcram/WordAngler.java +20 -0
- data/src/wordcram/WordArray.java +18 -0
- data/src/wordcram/WordBag.java +31 -0
- data/src/wordcram/WordColorer.java +25 -0
- data/src/wordcram/WordCounter.java +96 -0
- data/src/wordcram/WordCram.java +920 -0
- data/src/wordcram/WordCramEngine.java +196 -0
- data/src/wordcram/WordFonter.java +24 -0
- data/src/wordcram/WordNudger.java +44 -0
- data/src/wordcram/WordPlacer.java +44 -0
- data/src/wordcram/WordRenderer.java +10 -0
- data/src/wordcram/WordShaper.java +78 -0
- data/src/wordcram/WordSizer.java +46 -0
- data/src/wordcram/WordSkipReason.java +42 -0
- data/src/wordcram/WordSorterAndScaler.java +31 -0
- data/src/wordcram/WordSource.java +5 -0
- data/src/wordcram/text/Html.java +15 -0
- data/src/wordcram/text/Html2Text.java +17 -0
- data/src/wordcram/text/Text.java +15 -0
- data/src/wordcram/text/TextFile.java +23 -0
- data/src/wordcram/text/TextSource.java +5 -0
- data/src/wordcram/text/WebPage.java +23 -0
- metadata +94 -5
- data/lib/cue.language.jar +0 -0
- data/lib/jsoup-1.7.2.jar +0 -0
- data/vendors/Rakefile +0 -51
@@ -0,0 +1,196 @@
|
|
1
|
+
package wordcram;
|
2
|
+
|
3
|
+
import java.awt.Color; // awt: turns P5-land color into awt color, for renderers
|
4
|
+
import java.awt.Shape; // awt: word->shape, skip if too small, pass it on
|
5
|
+
import java.awt.geom.Rectangle2D; // awt: to get bounding box width & height
|
6
|
+
import java.util.ArrayList;
|
7
|
+
import java.util.ListIterator;
|
8
|
+
|
9
|
+
import processing.core.PFont;
|
10
|
+
import processing.core.PVector;
|
11
|
+
|
12
|
+
class WordCramEngine {
|
13
|
+
|
14
|
+
private final WordRenderer renderer;
|
15
|
+
|
16
|
+
private final WordFonter fonter;
|
17
|
+
private final WordSizer sizer;
|
18
|
+
private final WordColorer colorer;
|
19
|
+
private final WordAngler angler;
|
20
|
+
private final WordPlacer placer;
|
21
|
+
private final WordNudger nudger;
|
22
|
+
|
23
|
+
private final ArrayList<EngineWord> eWords;
|
24
|
+
private final ListIterator<EngineWord> eWordIter;
|
25
|
+
private final ArrayList<EngineWord> drawnWords = new ArrayList<>();
|
26
|
+
private final ArrayList<Word> skippedWords = new ArrayList<>();
|
27
|
+
|
28
|
+
private final RenderOptions renderOptions;
|
29
|
+
private final Observer observer;
|
30
|
+
|
31
|
+
// TODO Damn, really need to break down that list of arguments.
|
32
|
+
WordCramEngine(WordRenderer renderer, Word[] words, WordFonter fonter, WordSizer sizer, WordColorer colorer, WordAngler angler, WordPlacer placer, WordNudger nudger, WordShaper shaper, BBTreeBuilder bbTreeBuilder, RenderOptions renderOptions, Observer observer) {
|
33
|
+
this.renderer = renderer;
|
34
|
+
|
35
|
+
this.fonter = fonter;
|
36
|
+
this.sizer = sizer;
|
37
|
+
this.colorer = colorer;
|
38
|
+
this.angler = angler;
|
39
|
+
this.placer = placer;
|
40
|
+
this.nudger = nudger;
|
41
|
+
this.observer = observer;
|
42
|
+
|
43
|
+
this.renderOptions = renderOptions;
|
44
|
+
this.eWords = wordsIntoEngineWords(words, shaper, bbTreeBuilder);
|
45
|
+
this.eWordIter = eWords.listIterator();
|
46
|
+
}
|
47
|
+
|
48
|
+
private ArrayList<EngineWord> wordsIntoEngineWords(Word[] words, WordShaper wordShaper, BBTreeBuilder bbTreeBuilder) {
|
49
|
+
ArrayList<EngineWord> engineWords = new ArrayList<>();
|
50
|
+
|
51
|
+
int maxNumberOfWords = words.length;
|
52
|
+
if (renderOptions.maxNumberOfWordsToDraw >= 0) {
|
53
|
+
maxNumberOfWords = Math.min(maxNumberOfWords, renderOptions.maxNumberOfWordsToDraw);
|
54
|
+
}
|
55
|
+
|
56
|
+
for (int i = 0; i < maxNumberOfWords; i++) {
|
57
|
+
Word word = words[i];
|
58
|
+
EngineWord eWord = new EngineWord(word, i, words.length, bbTreeBuilder);
|
59
|
+
|
60
|
+
PFont wordFont = word.getFont(fonter);
|
61
|
+
float wordSize = word.getSize(sizer, i, words.length);
|
62
|
+
float wordAngle = word.getAngle(angler);
|
63
|
+
|
64
|
+
Shape shape = wordShaper.getShapeFor(eWord.word.word, wordFont, wordSize, wordAngle);
|
65
|
+
if (isTooSmall(shape, renderOptions.minShapeSize)) {
|
66
|
+
skipWord(word, WordSkipReason.SHAPE_WAS_TOO_SMALL);
|
67
|
+
}
|
68
|
+
else {
|
69
|
+
eWord.setShape(shape, renderOptions.wordPadding);
|
70
|
+
engineWords.add(eWord); // DON'T add eWords with no shape.
|
71
|
+
}
|
72
|
+
}
|
73
|
+
|
74
|
+
for (int i = maxNumberOfWords; i < words.length; i++) {
|
75
|
+
skipWord(words[i], WordSkipReason.WAS_OVER_MAX_NUMBER_OF_WORDS);
|
76
|
+
}
|
77
|
+
|
78
|
+
return engineWords;
|
79
|
+
}
|
80
|
+
|
81
|
+
private boolean isTooSmall(Shape shape, int minShapeSize) {
|
82
|
+
if (minShapeSize < 1) {
|
83
|
+
minShapeSize = 1;
|
84
|
+
}
|
85
|
+
Rectangle2D r = shape.getBounds2D();
|
86
|
+
|
87
|
+
// Most words will be wider than tall, so this basically boils down to height.
|
88
|
+
// For the odd word like "I", we check width, too.
|
89
|
+
return r.getHeight() < minShapeSize || r.getWidth() < minShapeSize;
|
90
|
+
}
|
91
|
+
|
92
|
+
private void skipWord(Word word, WordSkipReason reason) {
|
93
|
+
// TODO delete these properties when starting a sketch, in case it's a re-run w/ the same words.
|
94
|
+
// NOTE: keep these as properties, because they (will be) deleted when the WordCramEngine re-runs.
|
95
|
+
word.wasSkippedBecause(reason);
|
96
|
+
skippedWords.add(word);
|
97
|
+
observer.wordSkipped(word);
|
98
|
+
}
|
99
|
+
|
100
|
+
boolean hasMore() {
|
101
|
+
return eWordIter.hasNext();
|
102
|
+
}
|
103
|
+
|
104
|
+
void drawAll() {
|
105
|
+
observer.beginDraw();
|
106
|
+
while(hasMore()) {
|
107
|
+
drawNext();
|
108
|
+
}
|
109
|
+
renderer.finish();
|
110
|
+
observer.endDraw();
|
111
|
+
}
|
112
|
+
|
113
|
+
void drawNext() {
|
114
|
+
if (!hasMore()) return;
|
115
|
+
EngineWord eWord = eWordIter.next();
|
116
|
+
boolean wasPlaced = placeWord(eWord);
|
117
|
+
if (wasPlaced) { // TODO unit test (somehow)
|
118
|
+
drawWordImage(eWord);
|
119
|
+
observer.wordDrawn(eWord.word);
|
120
|
+
}
|
121
|
+
}
|
122
|
+
|
123
|
+
private boolean placeWord(EngineWord eWord) {
|
124
|
+
Word word = eWord.word;
|
125
|
+
Rectangle2D rect = eWord.getShape().getBounds2D(); // TODO can we move these into EngineWord.setDesiredLocation? Does that make sense?
|
126
|
+
int wordImageWidth = (int)rect.getWidth();
|
127
|
+
int wordImageHeight = (int)rect.getHeight();
|
128
|
+
|
129
|
+
eWord.setDesiredLocation(placer, eWords.size(), wordImageWidth, wordImageHeight, renderer.getWidth(), renderer.getHeight());
|
130
|
+
|
131
|
+
// Set maximum number of placement trials
|
132
|
+
int maxAttemptsToPlace = renderOptions.maxAttemptsToPlaceWord > 0 ?
|
133
|
+
renderOptions.maxAttemptsToPlaceWord :
|
134
|
+
calculateMaxAttemptsFromWordWeight(word);
|
135
|
+
|
136
|
+
EngineWord lastCollidedWith = null;
|
137
|
+
for (int attempt = 0; attempt < maxAttemptsToPlace; attempt++) {
|
138
|
+
|
139
|
+
eWord.nudge(nudger.nudgeFor(word, attempt));
|
140
|
+
|
141
|
+
PVector loc = eWord.getCurrentLocation();
|
142
|
+
if (loc.x < 0 || loc.y < 0 || loc.x + wordImageWidth >= renderer.getWidth() || loc.y + wordImageHeight >= renderer.getHeight()) {
|
143
|
+
continue;
|
144
|
+
}
|
145
|
+
|
146
|
+
if (lastCollidedWith != null && eWord.overlaps(lastCollidedWith)) {
|
147
|
+
continue;
|
148
|
+
}
|
149
|
+
|
150
|
+
boolean foundOverlap = false;
|
151
|
+
for (EngineWord otherWord : drawnWords) {
|
152
|
+
if (eWord.overlaps(otherWord)) {
|
153
|
+
foundOverlap = true;
|
154
|
+
lastCollidedWith = otherWord;
|
155
|
+
break;
|
156
|
+
}
|
157
|
+
}
|
158
|
+
|
159
|
+
if (!foundOverlap) {
|
160
|
+
eWord.finalizeLocation();
|
161
|
+
return true;
|
162
|
+
}
|
163
|
+
}
|
164
|
+
|
165
|
+
skipWord(eWord.word, WordSkipReason.NO_SPACE);
|
166
|
+
return false;
|
167
|
+
}
|
168
|
+
|
169
|
+
private int calculateMaxAttemptsFromWordWeight(Word word) {
|
170
|
+
return (int)((1.0 - word.weight) * 600) + 100;
|
171
|
+
}
|
172
|
+
|
173
|
+
private void drawWordImage(EngineWord word) {
|
174
|
+
drawnWords.add(word);
|
175
|
+
renderer.drawWord(word, new Color(word.word.getColor(colorer), true));
|
176
|
+
}
|
177
|
+
|
178
|
+
Word getWordAt(float x, float y) {
|
179
|
+
int size = drawnWords.size() - 1;
|
180
|
+
for (int i = size; i >= 0; i--) {
|
181
|
+
EngineWord eWord = drawnWords.get(i);
|
182
|
+
if (eWord.containsPoint(x, y)) {
|
183
|
+
return eWord.word;
|
184
|
+
}
|
185
|
+
}
|
186
|
+
return null;
|
187
|
+
}
|
188
|
+
|
189
|
+
Word[] getSkippedWords() {
|
190
|
+
return skippedWords.toArray(new Word[0]);
|
191
|
+
}
|
192
|
+
|
193
|
+
float getProgress() {
|
194
|
+
return (float) eWordIter.nextIndex() / eWords.size();
|
195
|
+
}
|
196
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
package wordcram;
|
2
|
+
|
3
|
+
import processing.core.PFont;
|
4
|
+
|
5
|
+
/**
|
6
|
+
* A WordFonter tells WordCram what font to render a word in.
|
7
|
+
* <p>
|
8
|
+
* Some useful implementations are available in {@link Fonters}.
|
9
|
+
* <p>
|
10
|
+
* <i>The name "WordFonter" was picked because it matches the others: WordColorer,
|
11
|
+
* WordPlacer, WordSizer, etc. Apologies if it sounds a bit weird to your ear; I
|
12
|
+
* eventually got used to it.</i>
|
13
|
+
*
|
14
|
+
* @author Dan Bernier
|
15
|
+
*/
|
16
|
+
public interface WordFonter {
|
17
|
+
|
18
|
+
/**
|
19
|
+
* What font should this {@link Word} be drawn in?
|
20
|
+
* @param word the word to pick the PFont for
|
21
|
+
* @return the PFont for the word
|
22
|
+
*/
|
23
|
+
public PFont fontFor(Word word);
|
24
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
package wordcram;
|
2
|
+
|
3
|
+
import processing.core.PVector;
|
4
|
+
|
5
|
+
/**
|
6
|
+
* Once a WordPlacer tells WordCram where a word <i>should</i> go, a WordNudger
|
7
|
+
* tells WordCram how to nudge it around the field, until it fits in with the
|
8
|
+
* other words around it, or the WordCram gives up on the word.
|
9
|
+
* <p>
|
10
|
+
* WordCram gets a PVector from the nudger, and adds it to the word's desired
|
11
|
+
* location, to find the next spot to try fitting the word. Note that the
|
12
|
+
* PVectors returned from a nudger <i><b>don't accumulate</b></i>: if the placer
|
13
|
+
* puts a Word at (0, 0), and the nudger returns (1, 1), and then (2, 2),
|
14
|
+
* WordCram will try the word at (1, 1), and then (2, 2) -- <i>not</i> (1, 1)
|
15
|
+
* and then (3, 3).
|
16
|
+
* <p>
|
17
|
+
* A WordNudger should probably start nudging the word only a little, to keep it
|
18
|
+
* near its desired location, and gradually nudge it more and more, so that,
|
19
|
+
* even if the desired area is congested, the word can still fit in somewhere.
|
20
|
+
* This is why the WordCram passes in <code>attemptNumber</code>: it's the
|
21
|
+
* number of times it's attempted to place the word. This could (for example)
|
22
|
+
* scale the PVector, since the nudges don't accumulate (see above).
|
23
|
+
*
|
24
|
+
* @see RandomWordNudger
|
25
|
+
* @see SpiralWordNudger
|
26
|
+
*
|
27
|
+
* @author Dan Bernier
|
28
|
+
*/
|
29
|
+
public interface WordNudger {
|
30
|
+
|
31
|
+
/**
|
32
|
+
* How should this word be nudged, this time?
|
33
|
+
*
|
34
|
+
* @param word
|
35
|
+
* the word to nudge
|
36
|
+
* @param attemptNumber
|
37
|
+
* how many times WordCram has tried to place this word; starts
|
38
|
+
* at zero, and ends at
|
39
|
+
* <code>(int)((1.0-word.weight) * 600) + 100</code>
|
40
|
+
* @return the PVector to add to the word's desired location, to get the
|
41
|
+
* next spot to try fitting the word
|
42
|
+
*/
|
43
|
+
public PVector nudgeFor(Word word, int attemptNumber);
|
44
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
package wordcram;
|
2
|
+
|
3
|
+
import processing.core.PVector;
|
4
|
+
|
5
|
+
/**
|
6
|
+
* A WordPlacer tells WordCram where to place a word (in x,y coordinates) on the field.
|
7
|
+
* <p>
|
8
|
+
* A WordPlacer only suggests: the WordCram will try to place the Word where the
|
9
|
+
* WordPlacer tells it to, but if the Word overlaps other Words, a WordNudger
|
10
|
+
* will suggest different near-by spots for the Word until it fits, or until the
|
11
|
+
* WordCram gives up.
|
12
|
+
* <p>
|
13
|
+
* Some useful implementations are available in {@link Placers}.
|
14
|
+
*
|
15
|
+
* @author Dan Bernier
|
16
|
+
*/
|
17
|
+
public interface WordPlacer {
|
18
|
+
|
19
|
+
/**
|
20
|
+
* Where should this {@link Word} be drawn on the field?
|
21
|
+
*
|
22
|
+
* @param word
|
23
|
+
* The Word to place. A typical WordPlacer might use the Word's
|
24
|
+
* weight.
|
25
|
+
* @param wordIndex
|
26
|
+
* The index (rank) of the Word to place. Since this isn't a
|
27
|
+
* property of the Word, it's passed in as well.
|
28
|
+
* @param wordsCount
|
29
|
+
* The total number of words. Gives a context to wordIndex:
|
30
|
+
* "Word {wordIndex} of {wordsCount}".
|
31
|
+
* @param wordImageWidth
|
32
|
+
* The width of the word image.
|
33
|
+
* @param wordImageHeight
|
34
|
+
* The height of the word image.
|
35
|
+
* @param fieldWidth
|
36
|
+
* The width of the field.
|
37
|
+
* @param fieldHeight
|
38
|
+
* The height of the field.
|
39
|
+
* @return the desired location for a Word on the field, as a 2D PVector.
|
40
|
+
*/
|
41
|
+
public abstract PVector place(Word word, int wordIndex, int wordsCount,
|
42
|
+
int wordImageWidth, int wordImageHeight, int fieldWidth,
|
43
|
+
int fieldHeight);
|
44
|
+
}
|
@@ -0,0 +1,78 @@
|
|
1
|
+
package wordcram;
|
2
|
+
|
3
|
+
import java.awt.Font; // awt: font+word=shape
|
4
|
+
import java.awt.Shape; // awt: font+word=shape
|
5
|
+
import java.awt.font.FontRenderContext; // awt: font+word=shape
|
6
|
+
import java.awt.font.GlyphVector; // awt: font+word=shape
|
7
|
+
import java.awt.geom.AffineTransform; // awt: move and rotate shape
|
8
|
+
import java.awt.geom.Rectangle2D; // awt: move shape
|
9
|
+
|
10
|
+
import processing.core.PFont;
|
11
|
+
|
12
|
+
public class WordShaper {
|
13
|
+
private final FontRenderContext frc = new FontRenderContext(null, true, true);
|
14
|
+
|
15
|
+
|
16
|
+
private boolean rightToLeft;
|
17
|
+
public WordShaper(boolean rightToLeft) {
|
18
|
+
this.rightToLeft = rightToLeft;
|
19
|
+
}
|
20
|
+
public WordShaper() {
|
21
|
+
this(false);
|
22
|
+
}
|
23
|
+
|
24
|
+
|
25
|
+
public Shape getShapeFor(String word, Font font, float fontSize, float angle) {
|
26
|
+
Shape shape = makeShape(word, sizeFont(font, fontSize));
|
27
|
+
return moveToOrigin(rotate(shape, angle));
|
28
|
+
}
|
29
|
+
|
30
|
+
public Shape getShapeFor(String word, Font font) {
|
31
|
+
return getShapeFor(word, font, font.getSize2D(), 0);
|
32
|
+
}
|
33
|
+
|
34
|
+
public Shape getShapeFor(String word, PFont pFont, float fontSize, float angle) {
|
35
|
+
return getShapeFor(word, (Font)pFont.getNative(), fontSize, angle);
|
36
|
+
}
|
37
|
+
|
38
|
+
public Shape getShapeFor(String word, PFont pFont) {
|
39
|
+
return getShapeFor(word, (Font)pFont.getNative());
|
40
|
+
}
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
private Font sizeFont(Font unsizedFont, float fontSize) {
|
45
|
+
if (fontSize == unsizedFont.getSize2D()) {
|
46
|
+
return unsizedFont;
|
47
|
+
}
|
48
|
+
return unsizedFont.deriveFont(fontSize);
|
49
|
+
}
|
50
|
+
|
51
|
+
private Shape makeShape(String word, Font font) {
|
52
|
+
char[] chars = word.toCharArray();
|
53
|
+
|
54
|
+
// TODO hmm: this doesn't render newlines. Hrm. If your word text is "foo\nbar", you get "foobar".
|
55
|
+
GlyphVector gv = font.layoutGlyphVector(frc, chars, 0, chars.length,
|
56
|
+
this.rightToLeft ? Font.LAYOUT_RIGHT_TO_LEFT : Font.LAYOUT_LEFT_TO_RIGHT);
|
57
|
+
|
58
|
+
return gv.getOutline();
|
59
|
+
}
|
60
|
+
|
61
|
+
private Shape rotate(Shape shape, float rotation) {
|
62
|
+
if (rotation == 0) {
|
63
|
+
return shape;
|
64
|
+
}
|
65
|
+
|
66
|
+
return AffineTransform.getRotateInstance(rotation).createTransformedShape(shape);
|
67
|
+
}
|
68
|
+
|
69
|
+
private Shape moveToOrigin(Shape shape) {
|
70
|
+
Rectangle2D rect = shape.getBounds2D();
|
71
|
+
|
72
|
+
if (rect.getX() == 0 && rect.getY() == 0) {
|
73
|
+
return shape;
|
74
|
+
}
|
75
|
+
|
76
|
+
return AffineTransform.getTranslateInstance(-rect.getX(), -rect.getY()).createTransformedShape(shape);
|
77
|
+
}
|
78
|
+
}
|
@@ -0,0 +1,46 @@
|
|
1
|
+
package wordcram;
|
2
|
+
|
3
|
+
/**
|
4
|
+
* A WordSizer tells WordCram how big to render each word.
|
5
|
+
* You'll pass a WordSizer to WordCram via {@link WordCram#withSizer(WordSizer)}.
|
6
|
+
* <p>
|
7
|
+
* Some useful implementations are available in {@link Sizers}.
|
8
|
+
*
|
9
|
+
* @author Dan Bernier
|
10
|
+
*/
|
11
|
+
public interface WordSizer {
|
12
|
+
|
13
|
+
/**
|
14
|
+
* How big should this {@link Word} be rendered?
|
15
|
+
* <p>
|
16
|
+
* Generally, a word cloud draws more important words bigger. Two typical
|
17
|
+
* ways to measure word's importance are its weight, and its rank (its
|
18
|
+
* position in the list of words, sorted by weight).
|
19
|
+
* <p>
|
20
|
+
* Given that, sizeFor is passed the Word (which knows its own weight), its
|
21
|
+
* rank, and the total number of words.
|
22
|
+
* <p>
|
23
|
+
* For example, given the text "I think I can I think", the words would look
|
24
|
+
* like this:
|
25
|
+
* <ul>
|
26
|
+
* <li>"I", weight 1.0 (3/3), rank 1</li>
|
27
|
+
* <li>"think", weight 0.667 (2/3), rank 2</li>
|
28
|
+
* <li>"can", weight 0.333 (1/3), rank 3</li>
|
29
|
+
* </ul>
|
30
|
+
* ...and the WordSizer would be called with the following values:
|
31
|
+
* <ul>
|
32
|
+
* <li>Word "I" (weight 1.0), 1, 3</li>
|
33
|
+
* <li>Word "think" (weight 0.667), 2, 3</li>
|
34
|
+
* <li>Word "can" (weight 0.333), 3, 3</li>
|
35
|
+
* </ul>
|
36
|
+
*
|
37
|
+
* @param word
|
38
|
+
* the Word to determine the size of
|
39
|
+
* @param wordRank
|
40
|
+
* the rank of the Word
|
41
|
+
* @param wordCount
|
42
|
+
* the total number of Words being rendered
|
43
|
+
* @return the size to render the Word
|
44
|
+
*/
|
45
|
+
public float sizeFor(Word word, int wordRank, int wordCount);
|
46
|
+
}
|