Skip to content

Commit

Permalink
TweetWallFX#1288 Emojify the tweet stream
Browse files Browse the repository at this point in the history
  • Loading branch information
svenreimers committed Oct 10, 2022
1 parent cf95626 commit 7abc193
Show file tree
Hide file tree
Showing 9 changed files with 316 additions and 10 deletions.
1 change: 1 addition & 0 deletions controls/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ dependencies {
implementation 'de.jensd:fontawesomefx-fontawesome:4.7.0-11'
implementation 'org.apache.logging.log4j:log4j-api:2.19.0'
implementation project(':tweetwallfx-core')
implementation project(':tweetwallfx-emoji')
implementation project(':tweetwallfx-stepengine-dataproviders')
implementation project(':tweetwallfx-transitions')
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
import org.tweetwallfx.tweet.api.Tweet;
import org.tweetwallfx.tweet.api.entry.MediaTweetEntry;
import org.tweetwallfx.tweet.api.entry.MediaTweetEntryType;
import org.tweetwallfx.emoji.control.EmojiFlow;

/**
* Infinite TweetStream Animation Step
Expand Down Expand Up @@ -251,24 +252,22 @@ private Pane createSingleTweetDisplay(
final Tweet displayTweet) {

String textWithoutMediaUrls = displayTweet.getDisplayEnhancedText();
Text text = new Text(textWithoutMediaUrls.replaceAll("[\n\r]+", config.respectLineFeeds ? "\n": "|"));
text.setCache(config.tweetTextNode.isCacheEnabled);
text.setCacheHint(config.tweetTextNode.cacheHint);
text.getStyleClass().add("tweetText");
String text = textWithoutMediaUrls.replaceAll("[\n\r]+", config.respectLineFeeds ? "\n": "|");
Node profileImageView = createProfileImageView(displayTweet);

TextFlow tweetFlow = new TextFlow(text);
EmojiFlow tweetFlow = new EmojiFlow();
tweetFlow.getStyleClass().add("tweetFlow");
tweetFlow.setText(text);
tweetFlow.setCache(config.tweetFlowNode.isCacheEnabled);
tweetFlow.setCacheHint(config.tweetFlowNode.cacheHint);
tweetFlow.setMinWidth(config.tweetWidth);
tweetFlow.setMaxWidth(config.tweetWidth);
tweetFlow.setPrefWidth(config.tweetWidth);
Text name = new Text(displayTweet.getUser().getName());
name.getStyleClass().add("tweetUsername");
name.setCache(config.speakerNameNode.isCacheEnabled);
name.setCacheHint(config.speakerNameNode.cacheHint);
TextFlow nameFlow = new TextFlow(name);

String name = displayTweet.getUser().getName();
EmojiFlow nameFlow = new EmojiFlow();
nameFlow.getStyleClass().add("tweetUsername");
nameFlow.setText(name);
nameFlow.setCache(config.tweetFlowNode.isCacheEnabled);
nameFlow.setCacheHint(config.tweetFlowNode.cacheHint);

Expand Down
29 changes: 29 additions & 0 deletions emoji/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2015-2022 TweetWallFX
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

dependencies {
api project(':tweetwallfx-cache')
implementation 'org.apache.logging.log4j:log4j-api:2.19.0'
implementation 'com.vdurmont:emoji-java:5.1.1'
}
46 changes: 46 additions & 0 deletions emoji/src/main/java/org/tweetwallfx/emoji/EmojiImageCache.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2022 TweetWallFX
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.tweetwallfx.emoji;

import org.tweetwallfx.cache.URLContent;
import org.tweetwallfx.cache.URLContentCacheBase;

/**
* Cache used to provide the avatar image of a User.
*/
public final class EmojiImageCache extends URLContentCacheBase {

/**
* Cache instance.
*/
public static final EmojiImageCache INSTANCE = new EmojiImageCache();

private EmojiImageCache() {
super("emojiImage");
}

public URLContent get(String hex) {
return EmojiImageCache.INSTANCE.getCachedOrLoad("https://twemoji.maxcdn.com/v/latest/72x72/"+ hex + ".png");
}
}
78 changes: 78 additions & 0 deletions emoji/src/main/java/org/tweetwallfx/emoji/Emojify.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2022 TweetWallFX
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.tweetwallfx.emoji;

import com.vdurmont.emoji.EmojiParser;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class Emojify {

@SuppressWarnings({"NarrowCalculation","StringSplitter"})
private static String convert(String unicodeStr) {
if (unicodeStr.isEmpty()) return unicodeStr;
List<String> parts = new ArrayList<String>();
int c = 0;
int p = 0;
for (int i=0; i<unicodeStr.length(); i++) {
c = unicodeStr.charAt(i);
if (p > 0) {
parts.add(Integer.toString(0x10000 + ((p - 0xD800) << 10) + (c - 0xDC00), 16));
p = 0;
} else if (0xD800 <= c && c <= 0xDBFF) {
p = c;
} else {
parts.add(Integer.toString(c,16));
p=0;
}
}
String dashedLogic = parts.stream().collect(Collectors.joining("-"));
return dashedLogic;
}

public static List<Object> tokenizeStringToTextAndEmoji(final String message) {
var emojiCandidates = new ArrayList<EmojiParser.UnicodeCandidate>();
EmojiParser.parseFromUnicode(message, new EmojiParser.EmojiTransformer() {
@Override
public String transform(EmojiParser.UnicodeCandidate unicodeCandidate) {
emojiCandidates.add(unicodeCandidate);
return "";
}
});
var objects = new ArrayList<Object>();
int startIndex = 0;
for (com.vdurmont.emoji.EmojiParser.UnicodeCandidate unicodeCandidate : emojiCandidates) {
String text = message.substring(startIndex, unicodeCandidate.getEmojiStartIndex());
if (!text.isEmpty()) {
objects.add(text);
}
startIndex = unicodeCandidate.getFitzpatrickEndIndex();
String hex = convert(unicodeCandidate.getEmoji().getUnicode());
objects.add(new Twemoji(hex));
}
objects.add(message.substring(startIndex, message.length()));
return objects;
}
}
27 changes: 27 additions & 0 deletions emoji/src/main/java/org/tweetwallfx/emoji/Twemoji.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2022 TweetWallFX
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.tweetwallfx.emoji;

public record Twemoji(String hex) {
}
97 changes: 97 additions & 0 deletions emoji/src/main/java/org/tweetwallfx/emoji/control/EmojiFlow.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2022 TweetWallFX
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.tweetwallfx.emoji.control;

import java.util.List;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.scene.text.TextFlow;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.tweetwallfx.emoji.EmojiImageCache;
import org.tweetwallfx.emoji.Emojify;
import org.tweetwallfx.emoji.Twemoji;

public class EmojiFlow extends TextFlow {

private static final Logger LOG = LogManager.getLogger(EmojiFlow.class);

private final ObjectProperty<String> textProperty = new SimpleObjectProperty<>();
private final ObjectProperty<Integer> emojiFitWidthProperty = new SimpleObjectProperty<>();
private final ObjectProperty<Integer> emojiFitHeightProperty = new SimpleObjectProperty<>();

public EmojiFlow() {
this.emojiFitWidthProperty.set(12);
this.emojiFitHeightProperty.set(12);
this.textProperty.addListener((o, old, text) -> updateContent(text));
}

public final String getText() {
return textProperty.get();
}

public final void setText(String text) {
textProperty.set(text);
}

private void updateContent(String message) {
List<Object> obs = Emojify.tokenizeStringToTextAndEmoji(message);
for (Object ob : obs) {
if (ob instanceof String s) {
Text textNode = new Text();
textNode.setText(s);
textNode.getStyleClass().add("tweetText");
textNode.applyCss();
this.getChildren().add(textNode);
continue;
} else if (ob instanceof Twemoji emoji) {
try {
this.getChildren().add(createEmojiImageNode(emoji));
} catch (RuntimeException e) {
LOG.error("Image with hex code: " + emoji.hex() + " appear not to exist in resources path", e);
Text textNode = new Text();
textNode.setText(emoji.hex());
textNode.getStyleClass().add("tweetText");
textNode.applyCss();
this.getChildren().add(textNode);
}
}
}
}

private ImageView createEmojiImageNode(Twemoji emoji) throws NullPointerException {
ImageView imageView = new ImageView();
imageView.setFitWidth(emojiFitWidthProperty.get());
imageView.setFitHeight(emojiFitHeightProperty.get());

imageView.setImage(new Image(EmojiImageCache.INSTANCE.get(emoji.hex()).getInputStream()));
return imageView;
}
}
28 changes: 28 additions & 0 deletions emoji/src/main/resources/tweetwallConfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"cacheConfiguration": {
"caches": {
"emojiImage": {
"keyType": "java.lang.String",
"valueType": "org.tweetwallfx.cache.URLContent",
"contentLoaderThreads": 1,
"expiry": {
"type": "TIME_TO_IDLE",
"amount": 10,
"unit": "DAYS"
},
"cacheResources": [
{
"type": "HEAP",
"amount": 100,
"unit": "MB"
},
{
"type": "DISK",
"amount": 1,
"unit": "GB"
}
]
}
}
}
}
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def includeWithName(String path, String name) {

includeWithName ':cache', 'tweetwallfx-cache'
includeWithName ':config', 'tweetwallfx-configuration'
includeWithName ':emoji', 'tweetwallfx-emoji'
includeWithName ':filterchain', 'tweetwallfx-filterchain'
includeWithName ':stepengine-api', 'tweetwallfx-stepengine-api'
includeWithName ':stepengine-dataproviders', 'tweetwallfx-stepengine-dataproviders'
Expand Down

0 comments on commit 7abc193

Please sign in to comment.