Idee für eine Automatische Methode, abhängig von der Schriftart automatisch Zeilenumbrüche in Text einzufügen, um eine gewisse maximale Breite einzuhalten?

4 Antworten

Vom Fragesteller als hilfreich ausgezeichnet

Ich hab mir jetzt nochmal die Mühe gemacht, ein Testformular in Java zu bauen, um deine These zu prüfen.

Bild zum Beitrag

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:

Bild zum Beitrag

Die Zeilenumbrüche können mit den berechneten Längen auch problemlos berechnet werden.

Bild zum Beitrag

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());
	}
}
 - (Computer, programmieren, Java)  - (Computer, programmieren, Java)  - (Computer, programmieren, Java)
PerfectMuffin 
Fragesteller
 17.08.2020, 12:27

Ja, Java kann seine eigene Breite korrekt berechnen, aber vergleich' das einmal mit LibreOffice writer

0
PerfectMuffin 
Fragesteller
 18.08.2020, 08:04

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!

0

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.

PerfectMuffin 
Fragesteller
 14.08.2020, 12:22

Könnte ich ja alles mit Java, nur kann Java nicht mit Schriftarten umgehen, wie es scheint.

0

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:

Bild zum Beitrag

Broadway, 10:

Bild zum Beitrag

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:

Bild zum Beitrag

Das Gleiche wie bei Broadway.

Es ist aber in allen Fällen nicht so, dass die Zeichen völlig falsch dargestellt werden.

 - (Computer, programmieren, Java)  - (Computer, programmieren, Java)  - (Computer, programmieren, Java)
PerfectMuffin 
Fragesteller
 17.08.2020, 18:18

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?

0
daCypher  18.08.2020, 08:42
@PerfectMuffin

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 👍

0
PerfectMuffin 
Fragesteller
 18.08.2020, 08:43
@daCypher

Geholfen ist untertrieben, hast praktisch 80% von meinem Miniprojekt für mich umgesetzt.

1

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.

PerfectMuffin 
Fragesteller
 14.08.2020, 23:09

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.

0