JavaFX - subscript and superscript text in TextFlow - java

I have been trying to get simple rich text in JavaFX: I wish for continuous text where some characters are bold, subscript, or superscript. This is impossible in the normal Text or Label classes. I have tried WebView with no success, as although it will display such text, it does not size to its contents, and thus takes up an uncontrollably large portion of the screen.
Now I am attempting to use a TextFlow. I can successfully link together Text objects, some of which can be made bold. However, subscript and superscript are proving more difficult. Subscript can be emulated simply by reducing the font size, however superscript needs that Text object to be raised above the others. I cannot find a way of doing this: TextFlow specifically ignores translation properties of the Text objects, and I cannot override getBaselineOffset() on the Text in question, as it is final.
Am I going to have to put the Texts in an HBox? Is there really no support for this in JavaFX? What I am trying to do is not complex; it seems mind-boggling that there is no native support for subscript and superscript.
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.scene.text.TextFlow;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class TextFlowBuilder
{
private static final String BOLD = "<b>";
private static final String UN_BOLD = "</b>";
private static final String SUPERSCRIPT = "<sup>";
private static final String UN_SUPERSCRIPT = "</sup>";
private static final String SUBSCRIPT = "<sub>";
private static final String UN_SUBSCRIPT = "</sub>";
private static final Pattern NOT_JUST_WHITESPACE = Pattern.compile("\\S");
private static final Pattern CHARACTER_CODE = Pattern.compile("&#(\\d+);");
public static TextFlow htmlToTextFlow(final String html, final int fontSize, final TextAlignment alignment)
{
final String[] split = html.split("(?<=>)|(?=<)"); //Split before and after tags, splitting it into a series of tags and tag contents.
final List<Text> texts = new LinkedList<>();
boolean b = false;
boolean sup = false;
boolean sub = false;
for (String segment : split)
{
switch (segment)
{
case BOLD:
b = true;
break;
case UN_BOLD:
b = false;
break;
case SUPERSCRIPT:
sup = true;
break;
case UN_SUPERSCRIPT:
sup = false;
break;
case SUBSCRIPT:
sub = true;
break;
case UN_SUBSCRIPT:
sub = false;
break;
default:
//Add as text if string is not a tag, and is more than just whitespace.
if (segment.length() > 0
&& NOT_JUST_WHITESPACE.matcher(segment).find()
&& !segment.startsWith("<"))
{
final Matcher m = CHARACTER_CODE.matcher(segment);
while (m.find())
{
final String specialChar = Character.toString((char)Integer.parseInt(m.group(1)));
segment = m.replaceFirst(specialChar);
}
final Text t = new Text(segment);
String style = "";
if (b)
style += "-fx-font-weight: bold; ";
if (sup)
{
style += String.format("-fx-font-size: %f.3; ", fontSize/1.75);
//Need to move text to above the rest
}
else if (sub)
{
style += String.format("-fx-font-size: %f.3; ", fontSize/1.75);
}
else
{
style += String.format("-fx-font-size: %d; ", fontSize);
}
t.setStyle(style);
texts.add(t);
}
}
}
final Text[] textsAsArray = new Text[texts.size()];
final TextFlow tf = new TextFlow(texts.toArray(textsAsArray));
tf.setTextAlignment(alignment);
return tf;
}
}

Setting the translateY property (in code, not sure about via CSS) on the text objects does indeed work - perhaps something has changed about it since the question was asked.
Here's a demonstration:
TextFlow container = new TextFlow();
Text normal = new Text("Normal");
Text sup = new Text("sup");
Text sub = new Text("sub");
sup.setTranslateY(normal.getFont().getSize() * -0.3);
sub.setTranslateY(normal.getFont().getSize() * 0.3);
container.getChildren().addAll(normal, sup, sub);
Here's what it looks like:
I am using JavaFX 11 on a Mac, in case that ends up making a difference to whether or not this actually works.

Use -fx-translate-y
You can control the text up and down
You can also use RichTextFX
that can be used to create an editable text area with varying styles and with Superscript and Subscript support

Related

How I can use InCombiningDiacriticalMarks ignoring one case

I'm writing code for remove all diacritics for one String.
For example: áÁéÉíÍóÓúÚäÄëËïÏöÖüÜñÑ
I'm using the property InCombiningDiacriticalMarks of Unicode. But I want to ignore the replace for ñ and Ñ.
Now I'm saving these two characters before replace with:
s = s.replace('ñ', '\001');
s = s.replace('Ñ', '\002');
It's possible to use InCombiningDiacriticalMarks ignoring the diacritic of ñ and Ñ.
This is my code:
public static String stripAccents(String s)
{
/*Save ñ*/
s = s.replace('ñ', '\001');
s = s.replace('Ñ', '\002');
s = Normalizer.normalize(s, Normalizer.Form.NFD);
s = s.replaceAll("[\\p{InCombiningDiacriticalMarks}]", "");
/*Add ñ to s*/
s = s.replace('\001', 'ñ');
s = s.replace('\002', 'Ñ');
return s;
}
It works fine, but I want know if it's possible optimize this code.
It depends what you mean by "optimize". It's tough to reduce the number of lines of code from what you have written, but since you are processing the string six times there's scope to improve performance by processing the input string only once, character by character:
public class App {
// See SO answer https://stackoverflow.com/a/10831704/2985643 by virgo47
private static final String tab00c0
= "AAAAAAACEEEEIIII"
+ "DNOOOOO\u00d7\u00d8UUUUYI\u00df"
+ "aaaaaaaceeeeiiii"
+ "\u00f0nooooo\u00f7\u00f8uuuuy\u00fey"
+ "AaAaAaCcCcCcCcDd"
+ "DdEeEeEeEeEeGgGg"
+ "GgGgHhHhIiIiIiIi"
+ "IiJjJjKkkLlLlLlL"
+ "lLlNnNnNnnNnOoOo"
+ "OoOoRrRrRrSsSsSs"
+ "SsTtTtTtUuUuUuUu"
+ "UuUuWwYyYZzZzZzF";
public static void main(String[] args) {
var input = "AaBbCcáÁéÉíÍóÓúÚäÄëËïÏöÖüÜñÑçÇ";
var output = removeDiacritic(input);
System.out.println("input = " + input);
System.out.println("output = " + output);
}
public static String removeDiacritic(String input) {
var output = new StringBuilder(input.length());
for (var c : input.toCharArray()) {
if (isModifiable(c)) {
c = tab00c0.charAt(c - '\u00c0');
}
output.append(c);
}
return output.toString();
}
// Returns true if the supplied char is a candidate for diacritic removal.
static boolean isModifiable(char c) {
boolean modifiable;
if (c < '\u00c0' || c > '\u017f') {
modifiable = false;
} else {
modifiable = switch (c) {
case 'ñ', 'Ñ' ->
false;
default ->
true;
};
}
return modifiable;
}
}
This is the output from running the code:
input = AaBbCcáÁéÉíÍóÓúÚäÄëËïÏöÖüÜñÑçÇ
output = AaBbCcaAeEiIoOuUaAeEiIoOuUñÑcC
Characters without diacritics in the input string are not modified. Otherwise the diacritic is removed (e.g. Çto C), except in the cases of ñ and Ñ.
Notes:
The code does not use the Normalizer class or InCombiningDiacriticalMarks at all. Instead it processes each character in the input string only once, removing its accent if appropriate. The conventional approach for removing diacritics (as used in the OP) does not support selective removal as far as I know.
The code is based on an answer by user virgo47, but enhanced to support the selective removal of accents. See virgo47's answer for details of mapping an accented character to its unaccented counterpart.
This solution only works for Latin-1/Latin-2, but could be enhanced to support other mappings.
Your solution is very short and easy to understand, but it feels brittle, and for large input I suspect that it would be significantly slower than an approach that only processed each character once.
Ave Maria Purisima,
You can create a pattern excluding the tilde from the diacritical marks set:
private static final Pattern STRIP_ACCENTS_PATTERN = Pattern.compile("[\\p{InCombiningDiacriticalMarks}&&[^\u0303]]+");
public static String stripAccents(String input) {
if (input == null) {
return null;
}
final StringBuilder decomposed = new StringBuilder(Normalizer.normalize(input, Normalizer.Form.NFD));
return STRIP_ACCENTS_PATTERN.matcher(decomposed).replaceAll(EMPTY);
}
Hope it helps

How to find a specific occurrence of a string inside of a string

The problem is this, I'm trying to find and get only one occurrence of a string, when the only way I can get one is by using a keyword that occurs multiple times.
Ex. 4 potato, 4 (string I want), 4 house, 4 car
How do I only get the string I want, when I can't type in any keywords that the string might contain.
Imagine it as trying to take only one paragraph out of an essay.
I've tried the stringy.replaceAll(Str1, Str2); variable, but to no avail. All that happens is I replace all of the string (go figure with a name like replace all)
package com.donovan.cunningham;
import java.util.ArrayList;
import java.util.Random;
public class EssayCreator {
//Creating varz
private static String[] lf = {"happy", "sad", "unhappy", "atractive",
"fast", "lazy"};
private static String[] op = {"estatic", "melhencohly", "depressed",
"alluring", "swift", "lackadaisical"};
private static String pF = " ";
private static String temp[];
private static String conv = " ";
private static String comm = ", ";
private static Random random = new Random();
private ArrayList<String> array = new ArrayList<String>();
public static void Converter(String in) {
in = in.replace(comm, conv);
for (int i = 0; i < lf.length; i++){
in = in.replace(lf[i], op[i]);
}
in = in.replace(conv, comm);
//int rand = random.nextInt(in.indexOf(pF));
for (int i = 0; i < in.indexOf(pF); i++){
/*
Where I want to get an exact string of an essay
I'd convert pF to conv, and then remove the paragraph to
change the order
} */
}
CreateGUI.output.setText(in);
Sound.stopSound();
}
}
Thanks
Your question is not very clear though. You want (1) to find the most occurrence string which you don't know or you want (2) to replace the occurrence string that you know?
The most naive way to do (1) is to chop your text by space and put them in a string-to-integer HashMap to calculate the most occurrence string. You can also scan this HashMap to find all the N-occurence strings
For (2), supposed that you already know which key string you want to find, you can apply indexOf(String str, int fromIndex) in String recursively as followed:
int occurenceCount = 0;
String input = "Here is your text with key_word1, key_word2, ...etc";
StringBuffer output = new StringBuffer();
int index = input.indexOf("key_word");
int copiedIndex = 0;
for(index>0)
{
output.append(input.substring(copiedIndex, index));
occurenceCount++;
if(occurenceCount==4) //Find 4th occurrence and replaced it with "new_key_word"
{
output.append("new_key_word")
}
else
{
output.append("key_word")
}
copiedIndex = index+("key_word".length);
index = input.indexOf("key_word", index+("key_word".length));
if(index==-1)
break;
}
Ref: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#indexOf(java.lang.String,%20int)
Not sure if I had answered your question though...
I've combed through your code and tried running it with a use case I made from the cryptic info you've given. As folks above are saying, it's not clear what you're trying to accomplish. Typically regex is your friend when trying to do more complex string pattern matching if String's built in methods are not enough. Try googling 'pattern match string in paragraph regex java' or something to that effect. Meanwhile, I've added some comments to your code which might help with making this question more clear. Happy to help if I can better understand what you're trying to do. See code and comments below:
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class EssayCreator {
// Creating varz
private static String[] lf = { "happy", "sad", "unhappy", "atractive",
"fast", "lazy" };
private static String[] op = { "estatic", "melhencohly", "depressed",
"alluring", "swift", "lackadaisical" };
private static String pF = " ";
private static String temp[];
private static String conv = " ";
private static String comm = ", ";
private static Random random = new Random();
private List<String> array = new ArrayList<String>();
// Bradley D: Just some side notes here:
// Don't capitalize method names and don't use nouns.
// They're not class names or constructors. Changed Converter to convert. It'd also be good to
// stipulate what you are converting, i.e. convertMyString to make this a
// little more intuitive
public static void convert(String in) {
/*
* Bradley D: First, you are replacing all commas following by a space
* with 3 spaces. Be good to know why you doin that?
*/
in = in.replace(comm, conv);
for (int i = 0; i < lf.length; i++) {
in = in.replace(lf[i], op[i]);
}
/*
* Bradley D: Now you are replacing the 3 spaces with a comma and a
* space again??
*/
in = in.replace(conv, comm);
// Bradley D: Not really sure what you are trying to iterate through
// here. in.indexOf(pF) is -1
// for the use case I've created for you with the text below (what did I
// miss?)...
// Perhaps you're trying to find the first place in your essay where pF
// (3 spaces) occurs....
// but you've already reconverted your 3 spaces back to a comma and
// single space, so I'm getting even more lost here....
// int rand = random.nextInt(in.indexOf(pF));
for (int i = 0; i < in.indexOf(pF); i++) {
/*
* Bradley D: What is pF?? It appears to be the same as comm...
*/
/*
* Where I want to get an exact string of an essay I'd convert pF to
* conv, and then remove the paragraph to change the order }
*/
// Bradley D: Ok, so if you can make this question clear, I'll give it a shot here
}
// Bradley D: Commenting this out since it was not included
// CreateGUI.output.setText(in);
// Sound.stopSound();
}
public static void main(String[] args) {
EssayCreator ec = new EssayCreator();
String essay = "Let's see if we find the desired string in here. "
+ "Are we happy? Nope, we're not happy. Who's happy? What does happiness mean anyway? "
+ "I'd be very happy if this question we're more clear, but let's give it a go anyway. Maybe "
+ "we're lazy, and that's not attractive, thus rendering us unhappy and lackadasical... jk! "
+ "So hey man... why are you replacing all of the commas with spaces? "
+ "Can you put comments in your code? What is pF? "
+ "Also you should not capitalize method names. They should be in camelCase and they should not "
+ "be nouns like Converter, which makes them look like a constructor. Methods represent "
+ "an action taken, so a verb to describe them is standard practice. "
+ "So use convert, but what are you converting? convertString?? convertWords? "
+ "Anyway, making your method names intuitive would be helpful to anyone trying to understand "
+ "the code.";
ec.convert(essay);
}
}

Wrap dynamic text automatically in Jbutton

I have a problem with my Jbuttons.
In my application, you can easily change the ui language and you can easily override the default translations for my buttons.
In this case, it is very unclear how long the text can be in a button, but my buttons have a fixed size (because of graphical harmony and so on).
Now my problem is, that I haven't found a solution to wrap a text with a inner button margin.
Example:
BUTTON 1:
"Hello"
-> Hello is short enough to be printed without newline.
BUTTON 2:
"Hello guys"
-> Due to the html tags, Hello guys will be automatically wrapped in two lines.
BUTTON 3:
"Hello g."
-> Hello g. fills exactly the width of the button. No word wrap by HTML!.
Now, the button 3 itself looks very crappy and overloaded.
Therefore I need a solution to automatically wrap a text which is wider or equal -4px than the button.
Additionally a text which doesn't contain a whitespace, should be wrapped either if it is too long.
A very easy solution for this problem is a utility method, I wrote.
Just call it in your *ButtonUI #paint method before you call super.paint(c,g);
e.g.:
if (c instanceof AbstractButton) {
String txt = button.getText();
button.setText(getWrappedText(g, button, txt));
}
Here is my formatter for free use (and for optimization, too ;) )
private static final String STR_NEWLINE = "<br />";
private FontRenderContext fontRenderContext = new FontRenderContext(new AffineTransform(), true, true);
private String getWrappedText(Graphics graphics, AbstractButton button, String str) {
if( str != null ) {
String text = str.replaceAll("<html><center>", "").replaceAll("</center></html>", "");
int width = button.getWidth();
Rectangle2D stringBounds = button.getFont().getStringBounds(text, fontRenderContext);
if ( !str.contains(STR_NEWLINE) && (width-5) < ((Double)stringBounds.getWidth()).intValue()) {
String newStr;
if( str.contains(" ") ) {
int lastIndex = str.lastIndexOf(" ");
newStr = str.substring(0, lastIndex)+STR_NEWLINE+str.substring(lastIndex);
} else {
int strLength = ((str.length()/3)*2);
newStr = str.substring(0, strLength)+STR_NEWLINE+str.substring(strLength);
}
return newStr;
}
}
return str;
}

How do I split/parse this String properly using Regex

I am inexperienced with regex and rusty with JAVA, so some help here would be appreciated.
So I have a String in the form:
statement|digit|statement
statement|digit|statement
etc.
where statement can be any combination of characters, digits, and spaces.
I want to parse this string such that I save the first and last statements of each line in a separate string array.
for example if I had a string:
cats|1|short hair and long hair
cats|2|black, blue
dogs|1|cats are better than dogs
I want to be able to parse the string into two arrays.
Array one = [cats], [cats], [dogs]
Array two = [short hair and long hair],[black, blue],[cats are better than dogs]
Matcher m = Pattern.compile("(\\.+)|\\d+|=(\\.+)").matcher(str);
while(m.find()) {
String key = m.group(1);
String value = m.group(2);
System.out.printf("key=%s, value=%s\n", key, value);
}
I would have continued to add the keys and values into seperate arrays had my output been right but no luck. Any help with this would be very much appreciated.
Here is a solution with RegEx:
public class ParseString {
public static void main(String[] args) {
String data = "cats|1|short hair and long hair\n"+
"cats|2|black, blue\n"+
"dogs|1|cats are better than dogs";
List<String> result1 = new ArrayList<>();
List<String> result2 = new ArrayList<>();
Pattern pattern = Pattern.compile("(.+)\\|\\d+\\|(.+)");
Matcher m = pattern.matcher(data);
while (m.find()) {
String key = m.group(1);
String value = m.group(2);
result1.add(key);
result2.add(value);
System.out.printf("key=%s, value=%s\n", key, value);
}
}
}
Here is a great site to help with regex http://txt2re.com/ expressions. Enter some example text in step one. Select the parts you are interested in part 2. And select a language in step 3. Then copy, paste and massage the code that it spits out.
Double split should work:
class ParseString
{
public static void main(String[] args)
{
String s = "cats|1|short hair and long hair\ncats|2|black, blue\ndogs|1|cats are better than dogs";
String[] sa1 = s.split("\n");
for (int i = 0; i < sa1.length; i++)
{
String[] sa2 = sa1[i].split("\\|");
System.out.printf("key=%s, value=%s\n", sa2[0], sa2[2]);
} // end for i
} // end main
} // end class ParseString
Output:
key=cats, value=short hair and long hair
key=cats, value=black, blue
key=dogs, value=cats are better than dogs
There is no need for a complex regex pattern, you could simple split the string by the demiliter token using the string's split method (String#split()) on Java.
Working Example
public class StackOverFlow31840211 {
private static final int SENTENCE1_TOKEN_INDEX = 0;
private static final int DIGIT_TOKEN_INDEX = SENTENCE1_TOKEN_INDEX + 1;
private static final int SENTENCE2_TOKEN_INDEX = DIGIT_TOKEN_INDEX + 1;
public static void main(String[] args) {
String[] text = {
"cats|1|short hair and long hair",
"cats|2|black, blue",
"dogs|1|cats are better than dogs"
};
ArrayList<String> arrayOne = new ArrayList<String>();
ArrayList<String> arrayTwo = new ArrayList<String>();
for (String s : text) {
String[] tokens = s.split("\\|");
int tokenType = 0;
for (String token : tokens) {
switch (tokenType) {
case SENTENCE1_TOKEN_INDEX:
arrayOne.add(token);
break;
case SENTENCE2_TOKEN_INDEX:
arrayTwo.add(token);
break;
}
++tokenType;
}
}
System.out.println("Sentences for first token: " + arrayOne);
System.out.println("Sentences for third token: " + arrayTwo);
}
}
I agree with the other answers that you should use split, but I am providing an answer that uses Pattern.split, since it uses a regex.
import java.util.*;
import java.lang.*;
import java.io.*;
import java.util.regex.Pattern;
/* Name of the class has to be "Main" only if the class is public. */
class MatchExample
{
public static void main (String[] args) {
String[] data = {
"cats|1|short hair and long hair",
"cats|2|black, blue",
"dogs|1|cats are better than dogs"
};
Pattern p = Pattern.compile("\\|\\d+\\|");
for(String line: data){
String[] elements = p.split(line);
System.out.println(elements[0] + " // " + elements[1]);
}
}
}
Notice that the pattern will match on one or more digits between two |'s. I see what you are doing with the groupings.
The main problem is that you need to escape | and not the .. Also what is the = doing in your regex? I generalized the regex a little bit but you can replace .* by \\d+ to have the same as you.
Matcher m = Pattern.compile("^(.+?)\\|.*\\|(.+)$", Pattern.MULTILINE).matcher(str);
Here is the strict version:"^([^|]+)\\|\\d+\\|([^|]+)$" (also with MULTILINE)
And it's indeed easier using split (on the lines) as some have said, but like this:
String[] parts = str.split("\\|\\d+\\|");
If parts.length is not two then you know it is not a legal line.
If your input is always formatted like that, then you can just do with this single statement to get the left part in the even indexes and the right part in the odd indexes (0: line1-left, 1: line1-right, 2: line2-left, 3: line2-right, 4: line3-left ...), so you will get an array twice the size of line count.
String[] parts = str.split("\\|\\d+\\||\\n+");

Using regex to define masks for numbers in Java

I am trying to define a set of rules, that will compute a mask based on the number it is given. For example I am trying to return a mask of 8472952424 of any number that start with 12, 13, 14, Or return 847235XXXX for any number that starts with 7 or 8.
The input numbers are 4 digit Integers and the return is a String. Do I need to convert the integers to string before I do the regex on them, and I am also not sure how to construct the expressions.
Edit
I have too much criteria to be done using separate if statements for each case. I am matching extension numbers to masks so it could be inserted correctly on Cisco CallManager database (in case you are curious)
Edit
This is what I have done for one of the cases but this is still not matching correctly:
public String lookupMask(int ext){
//convert to String
StringBuilder sb = new StringBuilder();
sb.append(ext);
String extString = sb.toString();
//compile and match pattern
Pattern p = Pattern.compile("^[12|13|14|15|17|19|42]");
Matcher m = p.matcher(extString);
if(m.matches()){
return "8472952424";
}
return null;
}
An example with Pattern could be this:
package test;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public class Main {
// working Pattern
private static final Pattern PATTERN = Pattern.compile("^((1[234579])|42)");
// Your Pattern won't work because although it takes in account the start of the
// input, the OR within a character class does not exempt you to write round brackets
// around sequential characters such as "12".
// In fact here, the OR will be interpreted as the "|" character in the class, thus
// allowing it as a start character.
private static final Pattern NON_WORKING_PATTERN = Pattern.compile("^[12|13|14|15|17|19|42]");
private static final String STARTS_WITH_1_234 = "8472952424";
private static final String STARTS_WITH_ANYTHING_ELSE = "847295XXXX";
public static void main(String[] args) {
// NON_WORKING_PATTERN "works" on "33333"
System.out.println(NON_WORKING_PATTERN.matcher("33333").find());
int[] testIntegers = new int[]{1200, 1300, 1400, 1500, 1700, 1900, 4200, 0000};
List<String> results = new ArrayList<String>();
for (int test: testIntegers) {
if (PATTERN.matcher(String.valueOf(test)).find()) {
results.add(STARTS_WITH_1_234);
}
else {
results.add(STARTS_WITH_ANYTHING_ELSE);
}
}
System.out.println(results);
}
}
Output:
true
[8472952424, 8472952424, 8472952424, 8472952424, 8472952424, 8472952424, 8472952424, 847295XXXX]

Categories