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,54 @@
|
|
1
|
+
package wordcram;
|
2
|
+
|
3
|
+
import processing.core.PApplet;
|
4
|
+
|
5
|
+
/**
|
6
|
+
* Sizers contains pre-made {@link WordSizer} implementations that you might
|
7
|
+
* find useful. Right now, it's only {@link Sizers#byWeight(int, int)} and
|
8
|
+
* {@link Sizers#byRank(int, int)}.
|
9
|
+
* @see WordSizer
|
10
|
+
*/
|
11
|
+
public class Sizers {
|
12
|
+
|
13
|
+
/**
|
14
|
+
* Returns a WordSizer that sizes words by their weight, where the
|
15
|
+
* "heaviest" word will be sized at <code>maxSize</code>.
|
16
|
+
* <p>
|
17
|
+
* To be specific, the font size for each word will be calculated with:
|
18
|
+
*
|
19
|
+
* <pre>
|
20
|
+
* PApplet.lerp(minSize, maxSize, (float) word.weight)
|
21
|
+
* </pre>
|
22
|
+
*
|
23
|
+
* @param minSize
|
24
|
+
* the size to draw a Word with weight = 0
|
25
|
+
* @param maxSize
|
26
|
+
* the size to draw a Word with weight = 1
|
27
|
+
* @return the WordSizer
|
28
|
+
*/
|
29
|
+
public static WordSizer byWeight(final int minSize, final int maxSize) {
|
30
|
+
return (Word word, int wordRank, int wordCount) -> PApplet.lerp(minSize, maxSize, word.weight);
|
31
|
+
}
|
32
|
+
|
33
|
+
/**
|
34
|
+
* Returns a WordSizer that sizes words by their rank. The first word will
|
35
|
+
* be sized at <code>maxSize</code>.
|
36
|
+
* <p>
|
37
|
+
* To be specific, the font size for each word will be calculated with:
|
38
|
+
*
|
39
|
+
* <pre>
|
40
|
+
* PApplet.map(wordRank, 0, wordCount, maxSize, minSize)
|
41
|
+
* </pre>
|
42
|
+
*
|
43
|
+
* @param minSize
|
44
|
+
* the size to draw the last Word
|
45
|
+
* @param maxSize
|
46
|
+
* the size to draw the first Word
|
47
|
+
* @return the WordSizer
|
48
|
+
*/
|
49
|
+
public static WordSizer byRank(final int minSize, final int maxSize) {
|
50
|
+
return (Word word, int wordRank, int wordCount) -> PApplet.map(wordRank, 0, wordCount, maxSize, minSize);
|
51
|
+
}
|
52
|
+
|
53
|
+
// TODO try exponent scales, rather than linear.
|
54
|
+
}
|
@@ -0,0 +1,70 @@
|
|
1
|
+
package wordcram;
|
2
|
+
|
3
|
+
import java.lang.reflect.InvocationTargetException;
|
4
|
+
import java.lang.reflect.Method;
|
5
|
+
import java.util.HashMap;
|
6
|
+
import processing.core.PApplet;
|
7
|
+
|
8
|
+
// This is a healthy rip-off of
|
9
|
+
// https://github.com/processing/processing/wiki/Library-Basics#adding-your-own-library-events
|
10
|
+
|
11
|
+
class SketchCallbackObserver implements Observer {
|
12
|
+
PApplet parent;
|
13
|
+
|
14
|
+
HashMap<String, Method> sketchMethods = new HashMap<>();
|
15
|
+
|
16
|
+
SketchCallbackObserver(PApplet parent) {
|
17
|
+
|
18
|
+
this.parent = parent;
|
19
|
+
|
20
|
+
registerSketchMethod("wordsCounted", Word[].class);
|
21
|
+
registerSketchMethod("beginDraw");
|
22
|
+
registerSketchMethod("wordDrawn", Word.class);
|
23
|
+
registerSketchMethod("wordSkipped", Word.class);
|
24
|
+
registerSketchMethod("endDraw");
|
25
|
+
}
|
26
|
+
|
27
|
+
@Override
|
28
|
+
public void wordsCounted(Word[] words) {
|
29
|
+
invoke("wordsCounted", new Object[] { words });
|
30
|
+
}
|
31
|
+
@Override
|
32
|
+
public void beginDraw() {
|
33
|
+
invoke("beginDraw", new Object[0]);
|
34
|
+
}
|
35
|
+
@Override
|
36
|
+
public void wordDrawn(Word word) {
|
37
|
+
invoke("wordDrawn", new Object[] { word });
|
38
|
+
}
|
39
|
+
@Override
|
40
|
+
public void wordSkipped(Word word) {
|
41
|
+
invoke("wordSkipped", new Object[] { word });
|
42
|
+
}
|
43
|
+
@Override
|
44
|
+
public void endDraw() {
|
45
|
+
invoke("endDraw", new Object[0]);
|
46
|
+
}
|
47
|
+
|
48
|
+
private void registerSketchMethod(String name, Class... parameterTypes) {
|
49
|
+
try {
|
50
|
+
Method method = parent.getClass().getMethod(name, parameterTypes);
|
51
|
+
sketchMethods.put(name, method);
|
52
|
+
}
|
53
|
+
catch (NoSuchMethodException | SecurityException e) {
|
54
|
+
// The sketch doesn't have this method name. No worries.
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
private void invoke(String name, Object[] arguments) {
|
59
|
+
if (sketchMethods.containsKey(name)) {
|
60
|
+
Method method = sketchMethods.get(name);
|
61
|
+
try {
|
62
|
+
method.invoke(parent, arguments);
|
63
|
+
}
|
64
|
+
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
|
65
|
+
System.err.println("Disabling method " + name + " because of an error.");
|
66
|
+
sketchMethods.remove(name);
|
67
|
+
}
|
68
|
+
}
|
69
|
+
}
|
70
|
+
}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
package wordcram;
|
2
|
+
|
3
|
+
import processing.core.PApplet;
|
4
|
+
import processing.core.PVector;
|
5
|
+
|
6
|
+
public class SpiralWordNudger implements WordNudger {
|
7
|
+
|
8
|
+
// Who knows? this seems to be good, but it seems to depend on the font --
|
9
|
+
// bigger fonts need a bigger thetaIncrement.
|
10
|
+
private float thetaIncrement = (float) (Math.PI * 0.03);
|
11
|
+
|
12
|
+
@Override
|
13
|
+
public PVector nudgeFor(Word w, int attempt) {
|
14
|
+
float rad = powerMap(0.6f, attempt, 0, 600, 1, 100);
|
15
|
+
|
16
|
+
thetaIncrement = powerMap(1, attempt, 0, 600, 0.5f, 0.3f);
|
17
|
+
float theta = thetaIncrement * attempt;
|
18
|
+
float x = PApplet.cos(theta) * rad;
|
19
|
+
float y = PApplet.sin(theta) * rad;
|
20
|
+
return new PVector(x, y);
|
21
|
+
}
|
22
|
+
|
23
|
+
private float powerMap(float power, float v, float min1, float max1,
|
24
|
+
float min2, float max2) {
|
25
|
+
|
26
|
+
float val = PApplet.norm(v, min1, max1);
|
27
|
+
val = PApplet.pow(val, power);
|
28
|
+
return PApplet.lerp(min2, max2, val);
|
29
|
+
}
|
30
|
+
|
31
|
+
}
|
@@ -0,0 +1,110 @@
|
|
1
|
+
package wordcram;
|
2
|
+
|
3
|
+
import java.awt.Color; // awt: color->hex
|
4
|
+
import java.awt.Shape; // awt: shape->path
|
5
|
+
import java.awt.geom.PathIterator; // awt: path->svg
|
6
|
+
import java.awt.geom.Path2D; // awt: path->svg
|
7
|
+
import java.io.PrintWriter;
|
8
|
+
import java.io.FileNotFoundException;
|
9
|
+
|
10
|
+
class SvgWordRenderer implements WordRenderer {
|
11
|
+
private final PrintWriter out;
|
12
|
+
private final int width;
|
13
|
+
private final int height;
|
14
|
+
|
15
|
+
SvgWordRenderer(String filename, int width, int height) throws FileNotFoundException {
|
16
|
+
this.out = new PrintWriter(filename);
|
17
|
+
this.width = width;
|
18
|
+
this.height = height;
|
19
|
+
|
20
|
+
pl("<?xml version=\"1.0\"?>");
|
21
|
+
pl("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\" \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">");
|
22
|
+
// TODO add wordcram metadata
|
23
|
+
pl("<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"" + width + "\" height=\"" + height + "\" fill-rule=\"evenodd\">"); // or nonzero
|
24
|
+
}
|
25
|
+
|
26
|
+
@Override
|
27
|
+
public int getWidth() {
|
28
|
+
return this.width;
|
29
|
+
}
|
30
|
+
|
31
|
+
@Override
|
32
|
+
public int getHeight() {
|
33
|
+
return this.height;
|
34
|
+
}
|
35
|
+
|
36
|
+
@Override
|
37
|
+
public void drawWord(EngineWord word, Color color) {
|
38
|
+
// TODO add word metadata
|
39
|
+
pl("<g style=\"fill:" + getColor(color) + "; stroke-width:0px\">");
|
40
|
+
renderShape(word.getShape());
|
41
|
+
pl("</g>");
|
42
|
+
}
|
43
|
+
|
44
|
+
@Override
|
45
|
+
public void finish() {
|
46
|
+
pl("</svg>");
|
47
|
+
out.flush();
|
48
|
+
out.close();
|
49
|
+
}
|
50
|
+
|
51
|
+
private String getColor(Color color) {
|
52
|
+
// rgb(30, 200, 90)
|
53
|
+
// #ff0023
|
54
|
+
return "#" + decToHex(color.getRed()) + decToHex(color.getGreen()) + decToHex(color.getBlue());
|
55
|
+
}
|
56
|
+
|
57
|
+
private String decToHex(int dec) {
|
58
|
+
return dec > 16 ? Integer.toHexString(dec) :
|
59
|
+
"0" + Integer.toHexString(dec);
|
60
|
+
}
|
61
|
+
|
62
|
+
private void renderShape(Shape shape) {
|
63
|
+
|
64
|
+
Path2D.Double path2d = new Path2D.Double(shape);
|
65
|
+
path2d.setWindingRule(Path2D.WIND_EVEN_ODD); // or WIND_NON_ZERO
|
66
|
+
PathIterator pathIter = path2d.getPathIterator(null);
|
67
|
+
|
68
|
+
float[] coords = new float[6];
|
69
|
+
|
70
|
+
p("<path d=\"");
|
71
|
+
while (!pathIter.isDone()) {
|
72
|
+
|
73
|
+
int type = pathIter.currentSegment(coords);
|
74
|
+
|
75
|
+
switch(type) {
|
76
|
+
case PathIterator.SEG_MOVETO:
|
77
|
+
p("M" + coords[0] + " " + coords[1]);
|
78
|
+
break;
|
79
|
+
|
80
|
+
case PathIterator.SEG_LINETO:
|
81
|
+
p("L" + coords[0] + " " + coords[1]);
|
82
|
+
break;
|
83
|
+
|
84
|
+
case PathIterator.SEG_QUADTO:
|
85
|
+
p("Q" + coords[0] + " " + coords[1] + " " + coords[2] + " " + coords[3]);
|
86
|
+
break;
|
87
|
+
|
88
|
+
case PathIterator.SEG_CUBICTO:
|
89
|
+
p("C" + coords[0] + " " + coords[1] + " " + coords[2] + " " + coords[3] + " " + coords[4] + " " + coords[5]);
|
90
|
+
break;
|
91
|
+
|
92
|
+
case PathIterator.SEG_CLOSE:
|
93
|
+
p("Z");
|
94
|
+
break;
|
95
|
+
}
|
96
|
+
|
97
|
+
pathIter.next();
|
98
|
+
}
|
99
|
+
|
100
|
+
pl("\"/>");
|
101
|
+
}
|
102
|
+
|
103
|
+
private void pl(String s) {
|
104
|
+
out.println(s);
|
105
|
+
}
|
106
|
+
|
107
|
+
private void p(String s) {
|
108
|
+
out.print(s);
|
109
|
+
}
|
110
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
package wordcram;
|
2
|
+
|
3
|
+
import processing.core.*;
|
4
|
+
|
5
|
+
public class SwirlWordPlacer implements WordPlacer {
|
6
|
+
|
7
|
+
@Override
|
8
|
+
public PVector place(Word word, int wordIndex, int wordsCount,
|
9
|
+
int wordImageWidth, int wordImageHeight, int fieldWidth,
|
10
|
+
int fieldHeight) {
|
11
|
+
|
12
|
+
float normalizedIndex = (float) wordIndex / wordsCount;
|
13
|
+
|
14
|
+
float theta = normalizedIndex * 6 * PConstants.TWO_PI;
|
15
|
+
float radius = normalizedIndex * fieldWidth / 2f;
|
16
|
+
|
17
|
+
float centerX = fieldWidth * 0.5f;
|
18
|
+
float centerY = fieldHeight * 0.5f;
|
19
|
+
|
20
|
+
float x = PApplet.cos(theta) * radius;
|
21
|
+
float y = PApplet.sin(theta) * radius;
|
22
|
+
|
23
|
+
return new PVector(centerX + x, centerY + y);
|
24
|
+
}
|
25
|
+
}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
package wordcram;
|
2
|
+
|
3
|
+
import java.util.Random;
|
4
|
+
|
5
|
+
import processing.core.PApplet;
|
6
|
+
import processing.core.PVector;
|
7
|
+
|
8
|
+
public class UpperLeftWordPlacer implements WordPlacer {
|
9
|
+
|
10
|
+
private final Random r = new Random();
|
11
|
+
|
12
|
+
@Override
|
13
|
+
public PVector place(Word word, int wordIndex, int wordsCount, int wordImageWidth, int wordImageHeight, int fieldWidth, int fieldHeight) {
|
14
|
+
int x = getOneUnder(fieldWidth - wordImageWidth);
|
15
|
+
int y = getOneUnder(fieldHeight - wordImageHeight);
|
16
|
+
return new PVector(x, y);
|
17
|
+
}
|
18
|
+
|
19
|
+
private int getOneUnder(int limit) {
|
20
|
+
return PApplet.round(PApplet.map(random(random(random(random(random(1.0f))))), 0, 1, 0, limit));
|
21
|
+
}
|
22
|
+
|
23
|
+
private float random(float limit) {
|
24
|
+
return r.nextFloat() * limit;
|
25
|
+
}
|
26
|
+
|
27
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
package wordcram;
|
2
|
+
|
3
|
+
import processing.core.*;
|
4
|
+
|
5
|
+
public class WaveWordPlacer implements WordPlacer {
|
6
|
+
|
7
|
+
@Override
|
8
|
+
public PVector place(Word word, int wordIndex, int wordsCount,
|
9
|
+
int wordImageWidth, int wordImageHeight, int fieldWidth,
|
10
|
+
int fieldHeight) {
|
11
|
+
|
12
|
+
float normalizedIndex = (float) wordIndex / wordsCount;
|
13
|
+
float x = normalizedIndex * fieldWidth;
|
14
|
+
float y = normalizedIndex * fieldHeight;
|
15
|
+
|
16
|
+
float yOffset = getYOffset(wordIndex, wordsCount, fieldHeight);
|
17
|
+
return new PVector(x, y + yOffset);
|
18
|
+
}
|
19
|
+
|
20
|
+
private float getYOffset(int wordIndex, int wordsCount, int fieldHeight) {
|
21
|
+
float theta = PApplet.map(wordIndex, 0, wordsCount, PConstants.PI, -PConstants.PI);
|
22
|
+
|
23
|
+
return (float) Math.sin(theta) * (fieldHeight / 3f);
|
24
|
+
}
|
25
|
+
}
|
@@ -0,0 +1,357 @@
|
|
1
|
+
package wordcram;
|
2
|
+
|
3
|
+
import java.util.HashMap;
|
4
|
+
|
5
|
+
import processing.core.PFont;
|
6
|
+
import processing.core.PVector;
|
7
|
+
|
8
|
+
/**
|
9
|
+
* A weighted word, for rendering in the word cloud image.
|
10
|
+
* <p>
|
11
|
+
* Each Word object has a {@link #word} String, and its associated {@link #weight}, and it's constructed
|
12
|
+
* with these two things.
|
13
|
+
*
|
14
|
+
*
|
15
|
+
* <h3>Hand-crafting Your Words</h3>
|
16
|
+
*
|
17
|
+
* If you're creating your own <code>Word[]</code> to pass
|
18
|
+
* to the WordCram (rather than using something like {@link WordCram#fromWebPage(String)}),
|
19
|
+
* you can specify how a Word should be rendered: set a Word's font, size, etc:
|
20
|
+
*
|
21
|
+
* <pre>
|
22
|
+
* Word w = new Word("texty", 20);
|
23
|
+
* w.setFont(createFont("myFontName", 1));
|
24
|
+
* w.setAngle(radians(45));
|
25
|
+
* </pre>
|
26
|
+
*
|
27
|
+
* Any values set on a Word will override the corresponding component ({@link WordColorer},
|
28
|
+
* {@link WordAngler}, etc) - it won't even be called for that word.
|
29
|
+
*
|
30
|
+
* <h3>Word Properties</h3>
|
31
|
+
* A word can also have properties. If you're creating custom components,
|
32
|
+
* you might want to send other information along with the word, for the components to use:
|
33
|
+
*
|
34
|
+
* <pre>
|
35
|
+
* Word strawberry = new Word("strawberry", 10);
|
36
|
+
* strawberry.setProperty("isFruit", true);
|
37
|
+
*
|
38
|
+
* Word pea = new Word("pea", 10);
|
39
|
+
* pea.setProperty("isFruit", false);
|
40
|
+
*
|
41
|
+
* new WordCram(this)
|
42
|
+
* .fromWords(new Word[] { strawberry, pea })
|
43
|
+
* .withColorer(new WordColorer() {
|
44
|
+
* public int colorFor(Word w) {
|
45
|
+
* if (w.getProperty("isFruit") == true) {
|
46
|
+
* return color(255, 0, 0);
|
47
|
+
* }
|
48
|
+
* else {
|
49
|
+
* return color(0, 200, 0);
|
50
|
+
* }
|
51
|
+
* }
|
52
|
+
* });
|
53
|
+
* </pre>
|
54
|
+
*
|
55
|
+
* @author Dan Bernier
|
56
|
+
*/
|
57
|
+
public class Word implements Comparable<Word> {
|
58
|
+
|
59
|
+
public String word;
|
60
|
+
public float weight;
|
61
|
+
|
62
|
+
private Float presetSize;
|
63
|
+
private Float presetAngle;
|
64
|
+
private PFont presetFont;
|
65
|
+
private Integer presetColor;
|
66
|
+
private PVector presetTargetPlace;
|
67
|
+
|
68
|
+
// These are null until they're rendered, and can be wiped out for a re-render.
|
69
|
+
private Float renderedSize;
|
70
|
+
private Float renderedAngle;
|
71
|
+
private PFont renderedFont;
|
72
|
+
private Integer renderedColor;
|
73
|
+
private PVector targetPlace;
|
74
|
+
private PVector renderedPlace;
|
75
|
+
private WordSkipReason skippedBecause;
|
76
|
+
|
77
|
+
private final HashMap<String,Object> properties = new HashMap<>();
|
78
|
+
|
79
|
+
public Word(String word, float weight) {
|
80
|
+
this.word = word;
|
81
|
+
this.weight = weight;
|
82
|
+
}
|
83
|
+
|
84
|
+
/**
|
85
|
+
* Set the size this Word should be rendered at - WordCram won't even call the WordSizer.
|
86
|
+
* @param size
|
87
|
+
* @return the Word, for more configuration
|
88
|
+
*/
|
89
|
+
public Word setSize(float size) {
|
90
|
+
this.presetSize = size;
|
91
|
+
return this;
|
92
|
+
}
|
93
|
+
|
94
|
+
/**
|
95
|
+
* Set the angle this Word should be rendered at - WordCram won't even call the WordAngler.
|
96
|
+
* @param angle
|
97
|
+
* @return the Word, for more configuration
|
98
|
+
*/
|
99
|
+
public Word setAngle(float angle) {
|
100
|
+
this.presetAngle = angle;
|
101
|
+
return this;
|
102
|
+
}
|
103
|
+
|
104
|
+
/**
|
105
|
+
* Set the font this Word should be rendered in - WordCram won't call the WordFonter.
|
106
|
+
* @param font
|
107
|
+
* @return the Word, for more configuration
|
108
|
+
*/
|
109
|
+
public Word setFont(PFont font) { // TODO provide a string overload? Will need the PApplet...
|
110
|
+
this.presetFont = font;
|
111
|
+
return this;
|
112
|
+
}
|
113
|
+
|
114
|
+
/**
|
115
|
+
* Set the color this Word should be rendered in - WordCram won't call the WordColorer.
|
116
|
+
* @param color
|
117
|
+
* @return the Word, for more configuration
|
118
|
+
*/
|
119
|
+
public Word setColor(int color) {
|
120
|
+
this.presetColor = color;
|
121
|
+
return this;
|
122
|
+
}
|
123
|
+
|
124
|
+
/**
|
125
|
+
* Set the place this Word should be rendered at - WordCram won't call the WordPlacer.
|
126
|
+
* @param place
|
127
|
+
* @return the Word, for more configuration
|
128
|
+
*/
|
129
|
+
public Word setPlace(PVector place) {
|
130
|
+
this.presetTargetPlace = place.copy();
|
131
|
+
return this;
|
132
|
+
}
|
133
|
+
|
134
|
+
/**
|
135
|
+
* Set the place this Word should be rendered at - WordCram won't call the WordPlacer.
|
136
|
+
* @param x
|
137
|
+
* @param y
|
138
|
+
* @return the Word, for more configuration
|
139
|
+
*/
|
140
|
+
public Word setPlace(float x, float y) {
|
141
|
+
this.presetTargetPlace = new PVector(x, y);
|
142
|
+
return this;
|
143
|
+
}
|
144
|
+
|
145
|
+
/*
|
146
|
+
* These methods are called by EngineWord: they return (for instance)
|
147
|
+
* either the color the user set via setColor(), or the value returned
|
148
|
+
* by the WordColorer. They're package-local, so they can't be called by the sketch.
|
149
|
+
*/
|
150
|
+
|
151
|
+
Float getSize(WordSizer sizer, int rank, int wordCount) {
|
152
|
+
renderedSize = presetSize != null ? presetSize : sizer.sizeFor(this, rank, wordCount);
|
153
|
+
return renderedSize;
|
154
|
+
}
|
155
|
+
|
156
|
+
Float getAngle(WordAngler angler) {
|
157
|
+
renderedAngle = presetAngle != null ? presetAngle : angler.angleFor(this);
|
158
|
+
return renderedAngle;
|
159
|
+
}
|
160
|
+
|
161
|
+
PFont getFont(WordFonter fonter) {
|
162
|
+
renderedFont = presetFont != null ? presetFont : fonter.fontFor(this);
|
163
|
+
return renderedFont;
|
164
|
+
}
|
165
|
+
|
166
|
+
Integer getColor(WordColorer colorer) {
|
167
|
+
renderedColor = presetColor != null ? presetColor : colorer.colorFor(this);
|
168
|
+
return renderedColor;
|
169
|
+
}
|
170
|
+
|
171
|
+
PVector getTargetPlace(WordPlacer placer, int rank, int count, int wordImageWidth, int wordImageHeight, int fieldWidth, int fieldHeight) {
|
172
|
+
targetPlace = presetTargetPlace != null ? presetTargetPlace : placer.place(this, rank, count, wordImageWidth, wordImageHeight, fieldWidth, fieldHeight);
|
173
|
+
return targetPlace;
|
174
|
+
}
|
175
|
+
|
176
|
+
void setRenderedPlace(PVector place) {
|
177
|
+
renderedPlace = place.copy();
|
178
|
+
}
|
179
|
+
|
180
|
+
/**
|
181
|
+
* Get the size the Word was rendered at: either the value passed to setSize(), or the value returned from the WordSizer.
|
182
|
+
* @return the rendered size
|
183
|
+
*/
|
184
|
+
public float getRenderedSize() {
|
185
|
+
return renderedSize;
|
186
|
+
}
|
187
|
+
|
188
|
+
/**
|
189
|
+
* Get the angle the Word was rendered at: either the value passed to setAngle(), or the value returned from the WordAngler.
|
190
|
+
* @return the rendered angle
|
191
|
+
*/
|
192
|
+
public float getRenderedAngle() {
|
193
|
+
return renderedAngle;
|
194
|
+
}
|
195
|
+
|
196
|
+
/**
|
197
|
+
* Get the font the Word was rendered in: either the value passed to setFont(), or the value returned from the WordFonter.
|
198
|
+
* @return the rendered font
|
199
|
+
*/
|
200
|
+
public PFont getRenderedFont() {
|
201
|
+
return renderedFont;
|
202
|
+
}
|
203
|
+
|
204
|
+
/**
|
205
|
+
* Get the color the Word was rendered in: either the value passed to setColor(), or the value returned from the WordColorer.
|
206
|
+
* @return the rendered color
|
207
|
+
*/
|
208
|
+
public int getRenderedColor() {
|
209
|
+
return renderedColor;
|
210
|
+
}
|
211
|
+
|
212
|
+
/**
|
213
|
+
* Get the place the Word was supposed to be rendered at: either the value passed to setPlace(),
|
214
|
+
* or the value returned from the WordPlacer.
|
215
|
+
* @return
|
216
|
+
*/
|
217
|
+
public PVector getTargetPlace() {
|
218
|
+
return targetPlace;
|
219
|
+
}
|
220
|
+
|
221
|
+
/**
|
222
|
+
* Get the final place the Word was rendered at, or null if it couldn't be placed.
|
223
|
+
* It returns the original target location (which is either the value passed to setPlace(),
|
224
|
+
* or the value returned from the WordPlacer), plus the nudge vector returned by the WordNudger.
|
225
|
+
* @return If word was placed, it's the (x,y) coordinates of the word's final location; else it's null.
|
226
|
+
*/
|
227
|
+
public PVector getRenderedPlace() {
|
228
|
+
return renderedPlace;
|
229
|
+
}
|
230
|
+
|
231
|
+
/**
|
232
|
+
* Indicates whether the Word was placed successfully. It's the same as calling word.getRenderedPlace() != null.
|
233
|
+
* If this returns false, it's either because a) WordCram didn't get to this Word yet,
|
234
|
+
* or b) it was skipped for some reason (see {@link #wasSkipped()} and {@link #wasSkippedBecause()}).
|
235
|
+
* @return true only if the word was placed.
|
236
|
+
*/
|
237
|
+
public boolean wasPlaced() {
|
238
|
+
return renderedPlace != null;
|
239
|
+
}
|
240
|
+
|
241
|
+
/**
|
242
|
+
* Indicates whether the Word was skipped.
|
243
|
+
* @see Word#wasSkippedBecause()
|
244
|
+
* @return true if the word was skipped
|
245
|
+
*/
|
246
|
+
public boolean wasSkipped() {
|
247
|
+
return wasSkippedBecause() != null;
|
248
|
+
}
|
249
|
+
|
250
|
+
/**
|
251
|
+
* Tells you why this Word was skipped.
|
252
|
+
*
|
253
|
+
* If the word was skipped, this will return a {@link WordSkipReason}
|
254
|
+
* explaining why.
|
255
|
+
*
|
256
|
+
* If the word was successfully placed, or WordCram hasn't
|
257
|
+
* gotten to this word yet, this will return null.
|
258
|
+
*
|
259
|
+
* @return the {@link WordSkipReason} why the word was skipped, or null if
|
260
|
+
* it wasn't skipped.
|
261
|
+
*/
|
262
|
+
public WordSkipReason wasSkippedBecause() {
|
263
|
+
return skippedBecause;
|
264
|
+
}
|
265
|
+
|
266
|
+
void wasSkippedBecause(WordSkipReason reason) {
|
267
|
+
skippedBecause = reason;
|
268
|
+
}
|
269
|
+
|
270
|
+
/**
|
271
|
+
* Get a property value from this Word, for a WordColorer, a WordPlacer, etc.
|
272
|
+
* @param propertyName
|
273
|
+
* @return the value of the property, or <code>null</code>, if it's not there.
|
274
|
+
*/
|
275
|
+
public Object getProperty(String propertyName) {
|
276
|
+
return properties.get(propertyName);
|
277
|
+
}
|
278
|
+
|
279
|
+
/**
|
280
|
+
* Set a property on this Word, to be used by a WordColorer, a WordPlacer, etc, down the line.
|
281
|
+
* @param propertyName
|
282
|
+
* @param propertyValue
|
283
|
+
* @return the Word, for more configuration
|
284
|
+
*/
|
285
|
+
public Word setProperty(String propertyName, Object propertyValue) {
|
286
|
+
properties.put(propertyName, propertyValue);
|
287
|
+
return this;
|
288
|
+
}
|
289
|
+
|
290
|
+
/**
|
291
|
+
* Displays the word, and its weight (in parentheses).
|
292
|
+
* <code>new Word("hello", 1.3).toString()</code> will return "hello (0.3)".
|
293
|
+
*/
|
294
|
+
@Override
|
295
|
+
public String toString() {
|
296
|
+
String status = "";
|
297
|
+
if (wasPlaced()) {
|
298
|
+
status = renderedPlace.x + "," + renderedPlace.y;
|
299
|
+
}
|
300
|
+
else if (wasSkipped()) {
|
301
|
+
status = skippedBecause.toString();
|
302
|
+
}
|
303
|
+
if (status.length() != 0) {
|
304
|
+
status = " [" + status + "]";
|
305
|
+
}
|
306
|
+
return word + " (" + weight + ")" + status;
|
307
|
+
}
|
308
|
+
|
309
|
+
/**
|
310
|
+
* Compares Words based on weight only. Words with equal weight are arbitrarily sorted.
|
311
|
+
* @param w
|
312
|
+
*/
|
313
|
+
@Override
|
314
|
+
public int compareTo(Word w) {
|
315
|
+
if (w.weight < weight) {
|
316
|
+
return -1;
|
317
|
+
}
|
318
|
+
else if (w.weight > weight) {
|
319
|
+
return 1;
|
320
|
+
}
|
321
|
+
else return 0;
|
322
|
+
}
|
323
|
+
|
324
|
+
// Note: these are only so we can delegate to EngineWord for getShape().
|
325
|
+
private EngineWord engineWord;
|
326
|
+
void setEngineWord(EngineWord engineWord) {
|
327
|
+
this.engineWord = engineWord;
|
328
|
+
}
|
329
|
+
|
330
|
+
|
331
|
+
// These are down here, because it comes from the EngineWord, *not* from the Fonter, Angler, etc.
|
332
|
+
/**
|
333
|
+
* Gets the width, in pixels, of the java.awt.Shape that will be rendered for
|
334
|
+
* this Word, based on the Font, Angle, and Size for this Word.
|
335
|
+
* If that all hasn't been figured out yet, then this returns 0.
|
336
|
+
* @return The width in pixels of this Word's Shape, or 0, if it hasn't
|
337
|
+
* been rendered yet.
|
338
|
+
* @see #getRenderedHeight()
|
339
|
+
*/
|
340
|
+
public float getRenderedWidth() {
|
341
|
+
if (engineWord == null) return 0.0f;
|
342
|
+
return (float)engineWord.getShape().getBounds2D().getWidth();
|
343
|
+
}
|
344
|
+
|
345
|
+
/**
|
346
|
+
* Gets the height, in pixels, of the java.awt.Shape that will be rendered for
|
347
|
+
* this Word, based on the Font, Angle, and Size for this Word.
|
348
|
+
* If that all hasn't been figured out yet, then this returns 0.
|
349
|
+
* @return The height in pixels of this Word's Shape, or 0, if it hasn't
|
350
|
+
* been rendered yet.
|
351
|
+
* @see #getRenderedWidth()
|
352
|
+
*/
|
353
|
+
public float getRenderedHeight() {
|
354
|
+
if (engineWord == null) return 0.0f;
|
355
|
+
return (float)engineWord.getShape().getBounds2D().getHeight();
|
356
|
+
}
|
357
|
+
}
|