With a preferred font list given,Main.main(...)
//simplified code
List<java.awt.Font> preferredFontList = List.of(
"Roboto-Regular.ttf", "FreeSerif.ttf", "Quivira-A8VL.ttf", "Code2000-rdLO.ttf"
).stream().map(fontName->createFont(fontName)).toList();
How can i fragment a string according to preferredFontList order and their support to display (Font.canDisplay(...)) the codePoints ? TS_FileTmcrFileHandler.addText(...)
//simplified code
var text = "Tuğalsan Karabacak ♠☀☁☃☎☛ ŞşİiIıÜüÖöÇ窺Ğğ";
fragment(text, preferredFontList).forEach((subText, decidedFontIdx)->{
addText(subText, preferredFontList.get(decidedFontIdx));
});
Your use of the word “fragment” confused me. It is not wrong, but it is vague. I am going to assume that you want break up (fragment) the string into substrings, each of which can be fully rendered by one of your preferred fonts.
A “text run” is a subsequence of characters which all have common attributes. In this case, that common attribute is the font which will be used to render the characters. Java has a class which can store text which has attribute-based runs in it: AttributedString.
So, we can create a loop which tries calling Font.canDisplayUpTo on each font, until that method returns a valid index, indicating that font can display at least one character. We can use the returned index to not only apply the font to parts of an AttributedString, but also to advance through the string and check the next part of the text with the same font loop, repeating that process until we reach the end of the string:
private AttributedString applyFontsTo(String text) {
AttributedString attrText = new AttributedString(text);
int len = text.length();
int textRunStart = 0;
CharacterIterator i = new StringCharacterIterator(text);
while (textRunStart >= 0) {
Font matchingFont = null;
String runText = null;
for (Font font : preferredFontList) {
int textRunEnd = font.canDisplayUpTo(i, textRunStart, len);
if (textRunEnd != textRunStart) {
matchingFont = font.deriveFont(24f);
attrText.addAttribute(TextAttribute.FONT, matchingFont,
textRunStart, textRunEnd >= 0 ? textRunEnd : len);
textRunStart = textRunEnd;
break;
}
}
if (matchingFont == null) {
int index = i.getIndex();
throw new IllegalArgumentException(String.format(
"Character at index %d (U+%04X) "
+ "cannot be displayed by any of %s",
index, text.codePointAt(index), preferredFontList));
}
}
return attrText;
}
We can now use the AttributedString’s iterator in a couple different ways:
Here is an example of the first approach:
public void show(String text) {
AttributedString a = applyFontsTo(text);
JPanel panel = new JPanel() {
private static final long serialVersionUID = 1;
@Override
public Dimension getPreferredSize() {
return new Dimension(text.length() * 20, 50);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
((Graphics2D) g).setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.drawString(a.getIterator(), 6, getHeight() - 12);
}
};
JFrame frame = new JFrame("Multi-Font Renderer");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(panel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
And here is an example of the second approach:
public void showAsList(String text) {
Box box = Box.createVerticalBox();
AttributedString a = applyFontsTo(text);
AttributedCharacterIterator i = a.getIterator();
while (i.getIndex() < i.getEndIndex()) {
int runLimit = i.getRunLimit();
String runText = text.substring(i.getIndex(), runLimit);
Font font = (Font) i.getAttribute(TextAttribute.FONT);
JLabel label = new JLabel(runText);
label.setFont(font);
box.add(label);
i.setIndex(runLimit);
}
JFrame frame = new JFrame("Multi-Font Renderer");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new JScrollPane(box));
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
Here is a complete program which demonstrates both approaches:
import java.io.InputStream;
import java.io.IOException;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.List;
import java.util.ArrayList;
import java.util.stream.Collectors;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.font.TextAttribute;
import javax.swing.Box;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
public class MultiFontRenderer {
private final List<Font> preferredFontList;
public MultiFontRenderer() {
preferredFontList = List.of(
"Roboto-Regular.ttf",
"FreeSerif.ttf",
"Quivira.otf",
"Code2000-rdLO.ttf"
).stream().map(fontName -> createFont(fontName)).collect(
Collectors.toUnmodifiableList());
}
private static Font createFont(String name) {
try (InputStream fontResource =
MultiFontRenderer.class.getResourceAsStream(name)) {
return Font.createFont(Font.TRUETYPE_FONT, fontResource);
} catch (IOException | FontFormatException e) {
throw new RuntimeException("Cannot load font \"" + name + "\"", e);
}
}
private AttributedString applyFontsTo(String text) {
AttributedString attrText = new AttributedString(text);
int len = text.length();
int textRunStart = 0;
CharacterIterator i = new StringCharacterIterator(text);
while (textRunStart >= 0) {
Font matchingFont = null;
String runText = null;
for (Font font : preferredFontList) {
int textRunEnd = font.canDisplayUpTo(i, textRunStart, len);
if (textRunEnd != textRunStart) {
matchingFont = font.deriveFont(24f);
attrText.addAttribute(TextAttribute.FONT, matchingFont,
textRunStart, textRunEnd >= 0 ? textRunEnd : len);
textRunStart = textRunEnd;
break;
}
}
if (matchingFont == null) {
int index = i.getIndex();
throw new IllegalArgumentException(String.format(
"Character at index %d (U+%04X) "
+ "cannot be displayed by any of %s",
index, text.codePointAt(index), preferredFontList));
}
}
return attrText;
}
public void show(String text) {
AttributedString a = applyFontsTo(text);
JPanel panel = new JPanel() {
private static final long serialVersionUID = 1;
@Override
public Dimension getPreferredSize() {
return new Dimension(text.length() * 20, 50);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
((Graphics2D) g).setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.drawString(a.getIterator(), 6, getHeight() - 12);
}
};
JFrame frame = new JFrame("Multi-Font Renderer");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(panel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public void showAsList(String text) {
Box box = Box.createVerticalBox();
AttributedString a = applyFontsTo(text);
AttributedCharacterIterator i = a.getIterator();
while (i.getIndex() < i.getEndIndex()) {
int runLimit = i.getRunLimit();
String runText = text.substring(i.getIndex(), runLimit);
Font font = (Font) i.getAttribute(TextAttribute.FONT);
JLabel label = new JLabel(runText);
label.setFont(font);
box.add(label);
i.setIndex(runLimit);
}
JFrame frame = new JFrame("Multi-Font Renderer");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new JScrollPane(box));
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
boolean showList = args.length > 0;
EventQueue.invokeLater(() -> {
var text = "Tuğalsan Karabacak ♠☀☁☃☎☛ ŞşİiIıÜüÖöÇ窺Ğğ";
MultiFontRenderer renderer = new MultiFontRenderer();
if (showList) {
renderer.showAsList(text);
} else {
renderer.show(text);
}
});
}
}