Idee für eine Automatische Methode, abhängig von der Schriftart automatisch Zeilenumbrüche in Text einzufügen, um eine gewisse maximale Breite einzuhalten?
Ich will in eine große Menge an Texten, die in spezielle Textboxen passen müssen, Zeilenumbrüche abhängig von der Schriftart entsprechend einsetzen, um den Text passend zu machen.
Ich wollte dazu zuerst ein Javaprogramm erstellen, aber dummerweise hat Java keinen Schimmer wie TTF funktioniert und zeigt die Zeichen so falsch an, dass es von der Breite her überhaupt nicht passt
(Sie ähneln der Windows Schriftartvorschau kaum).
LibreOffice zeigt die Zeichen angemessen an, ich sehe aber keine Möglichkeit, damit mein Ziel zu erreichen(Datei lesen, Textbreite prüfen, Datei schreiben...)
4 Antworten
Ich hab mir jetzt nochmal die Mühe gemacht, ein Testformular in Java zu bauen, um deine These zu prüfen.
Meiner Meinung nach wird der Text genau so angezeigt, wie er angezeigt werden sollte, obwohl es eine TTF-Schriftart ist. Das grüne Rechteck um den Text zeigt jeweils die berechnete Breite des Textes an.
Andere Schriftarten werden auch korrekt abgebildet:
Die Zeilenumbrüche können mit den berechneten Längen auch problemlos berechnet werden.
Falls das mit deiner eigenen Schriftart nicht funktioniert, behaupte ich einfach mal, dass an der Schriftart irgendwas faul ist.
Hier ist der Quelltext zu dem Formular, damit du selber damit rumspielen kannst:
package de.daCypher.textwidth;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FontDialog;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import java.util.Arrays;
import java.util.Deque;
import java.util.LinkedList;
import org.eclipse.swt.SWT;
import org.eclipse.wb.swt.SWTResourceManager;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
public class WidthViewer {
private static Text textBox;
private static Shell shell;
private static Display display;
private static final String defaultText =
"Dies ist ein Test.\n" +
"Wie man sieht, ist das Rechteck genau so groß, wie der Text.\n" +
"Die verwendete Schriftart ist Segoe UI, Größe 9.\n" +
"Hierbei handelt es sich um eine TTF-Schriftart.\n" +
"Bei Bedarf kann auch eine andere Schriftart über den \"Schriftart\"-Button ausgewählt werden.\n" +
"Klicke auf \"Zeilenumbrüche\", um die Zeilenumbrüche berechnen zu lassen.";
public static void main(String[] args) {
display = Display.getDefault();
shell = new Shell();
shell.setSize(250, 250);
shell.setText("Width Viewer");
shell.setLayout(new GridLayout(2, false));
textBox = new Text(shell, SWT.BORDER | SWT.MULTI);
textBox.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
textBox.addPaintListener(WidthViewer::paintStringBounds);
textBox.setText(defaultText);
Button btnFont = new Button(shell, SWT.NONE);
btnFont.addSelectionListener(SelectionListener.widgetSelectedAdapter(e->chooseFont(textBox)));
btnFont.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
btnFont.setText("Schriftart");
Button btnLineFeeds = new Button(shell, SWT.NONE);
btnLineFeeds.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
btnLineFeeds.addSelectionListener(SelectionListener.widgetSelectedAdapter(e->insertLineFeeds(textBox)));
btnLineFeeds.setText("Zeilenumbr\u00FCche");
shell.open();
shell.layout();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
/**
* Zeichnet ein Rechteck um den Text in jeder Zeile der Textbox.
* @param paintEvent
* Das {@link PaintEvent}, was vom {@link Text#addPaintListener(PaintListener)} übergeben wurde.
*/
private static void paintStringBounds(PaintEvent paintEvent) {
@SuppressWarnings("hiding")
Text textBox = (Text) paintEvent.widget;
String[] lines = textBox.getText().split("\\r?\\n");
int lineHeight = textBox.getLineHeight();
FontMetrics fontMetrics = paintEvent.gc.getFontMetrics();
paintEvent.gc.setForeground(display.getSystemColor(SWT.COLOR_GREEN));
int lineIndex = 0;
for (String line:lines) {
Point extent = paintEvent.gc.stringExtent(line);
paintEvent.gc.drawRectangle(fontMetrics.getLeading(), lineHeight*lineIndex++, extent.x, extent.y);
}
}
/**
* Öffnet einen Auswahldialog für Schriftarten
* @param forControl
* Das Steuerelement ({@link Control}), dem die ausgewählte Schriftart zugewiesen werden soll.
*/
private static void chooseFont(Control forControl) {
FontData fontData = new FontDialog(shell).open();
if (fontData != null)
forControl.setFont(SWTResourceManager.getFont(fontData.getName(), fontData.getHeight(), fontData.getStyle()));
}
/**
* Fügt Zeilenumbrüche in den bestehenden Text einer {@link Text}-Widgets ein.
* @param textBox
* Das {@link Text}-Widget, für das die Zeilenumbrüche berechnet werden sollen.
*/
private static void insertLineFeeds(@SuppressWarnings("hiding") Text textBox) {
String[] lines = textBox.getText().split("\\r?\\n");
StringBuilder fullText = new StringBuilder();
int maxWidth = textBox.getBounds().width - 10; // Links und rechts je 5 Pixel für den Textabstand zum Rand abziehen
GC graphics = new GC(textBox);
for (String line:lines) {
Deque<String> words = new LinkedList<String>();
words.addAll(Arrays.asList(line.split(" ")));
// Nach und nach Wörter in die widthCheckedLine einfügen und prüfen, ob sie passen.
// Falls sie nicht passen, wird ein Zeilenumbruch eingefügt.
// Ein Wort steht mindestens in jeder Zeile, auch wenn das Wort zu lang ist.
StringBuilder widthCheckedLine = new StringBuilder(words.pollFirst());
String currentLine = widthCheckedLine.toString();
for (String word:words) {
widthCheckedLine.append(" ").append(word);
if (graphics.stringExtent(widthCheckedLine.toString()).x > maxWidth) {
fullText.append(currentLine).append("\n");
widthCheckedLine = new StringBuilder(word);
}
currentLine = widthCheckedLine.toString();
}
fullText.append(currentLine).append("\n");
}
textBox.setText(fullText.toString());
}
}
Ja, Java kann seine eigene Breite korrekt berechnen, aber vergleich' das einmal mit LibreOffice writer
Das war eine Qual zum Laufen zu bringen, da ich diese Bibliotheken nicht parat hatte und Ausführen irgendwie nicht mit der gepackten SWT-Jar funktioniert, aber dein Programm zeigt den Text Einwandfrei an und ist extrem nah an der Textbreite des Zielprogrammes dran.
Dann muss ich meine Beschwerde präzisieren: Java SE kann nicht mit Schriftarten umgehen.
Jetzt habe ich eine programmierbare Plattform, die meinen Text richtig anzeigt, danke!
Den Text auf Basis eines bestimmten Fonts und einer bestimmten Fontgröße (Schriftgrad) zu formatieren ist schon ein wenig eigenartig.
Letztlich wirst Du um ein 'Testrendering' nicht herumkommen - Du könntest natürlich irgend eine andere Sprache nehmen und eine entsprechende Bibliothek.
Oder Du müßtest den TTF laden, die Parameter auslesen und so die Breite (unter angenommenem Schriftgrad) korrekt berechnen.Eventuell wirds leichter, wenn Du auf sowas wie FreeType oder harfbuzz zurückgreifst.
Deswegen ja der Vorschlag eine andere Sprache zu verwenden.
Ich kann leider keine Bilder in Kommentare einfügen, deshalb schreib ich das jetzt nochmal als neue Antwort.
Ich hab jetzt extra LibreOffice installiert, um zu sehen, ob es irgendwelche Unterschiede gibt, aber die Unterschiede sind vernachlässigbar.
Segoe UI, 9:
Broadway, 10:
Der Text wird in LibreOffice minimal breiter dargestellt. Da kannst du dem Java-Programm aber einfach sagen, dass es einfach 5% früher umbrechen soll.
Brush Script MT:
Das Gleiche wie bei Broadway.
Es ist aber in allen Fällen nicht so, dass die Zeichen völlig falsch dargestellt werden.
Das ist in der Tat sehr, sehr viel ähnlicher, als bei mir.
Ist vielleicht der Code, den ich zum Anzeigen benutze das Problem?
try{
font=Font.createFont(Font.TRUETYPE_FONT, new File("SourceHanSansK-Normal.ttf"));
}catch(Exception e){
System.out.println(e.getMessage());
}
ta = new TextArea("Test", 4, 256, TextArea.SCROLLBARS_HORIZONTAL_ONLY);
ta.setBounds(10, 400, 670, 108);
ta.setFont(font.deriveFont(0, 22f));
add(ta);
Funktioniert es mit Source Han Sans K-Normal bei dir?
Stimmt, in der AWT.TextArea zeigt er eine ganz andere Schriftart an. Egal, ob man den Font direkt aus einer Datei lädt oder sie zuerst installiert und dann mit "Font font = new Font("Source Han Sans K", Font.PLAIN, 22);" lädt.
Ich hab mich mit AWT noch nicht so sehr beschäftigt, weil ich aus faulheitsgründen lieber den WindowBuilder in Eclipse benutze, der nur SWT und JFace unterstützt.
Aber ich freu mich, dass ich dir helfen konnte 👍
Geholfen ist untertrieben, hast praktisch 80% von meinem Miniprojekt für mich umgesetzt.
Ich weiß nicht, was du so probiert hast, aber mit Java kann man die Breite von TTF-Schriftarten berechnen. Das sind die gleichen Schriftarten, die auch z.B. in AWT-Formularen angezeigt werden.
public static void main(String[] args) {
Font font = new Font("Arial", Font.PLAIN, 10);
FontRenderContext context = new FontRenderContext(new AffineTransform(), false, true);
String[] fiveLetterWords = { "Hallo", "Test ", " mit ", "Space", " "};
for (String word: fiveLetterWords) {
System.out.println(word + ": " + font.getStringBounds(word, context).getWidth());
}
}
Ergebnis:
Hallo: 22.7880859375
Test : 22.2265625
mit : 18.88671875
Space: 28.3544921875
: 13.8916015625
Was für eine Einheit das ist, weiß ich nicht. Müsste aber wahrscheinlich entweder Millimeter oder Punkte (wie bei der Schriftgröße sein).
Welche Schriftarten du auswählen kannst, kannst du mit
Arrays.stream(GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts()).forEach(System.out::println);
rauskriegen.
Wie bereits gesagt funktioniert das nicht, weil Java die Schriftarten falsch anzeigt.
Ich verwende übrigens keine Standardschriftart und bitte, nicht diesen Lambda Blödsinn. Nur weil man es bei jeder noch so kleinen for-each machen kann, heißt das noch lange nicht, dass man es muss oder sollte.
Könnte ich ja alles mit Java, nur kann Java nicht mit Schriftarten umgehen, wie es scheint.