Automatischer Zeilenumbruch

Wunderschönen Guten Tag,

ich habe mir einen “Zitate-Manager” gebaut, welcher aus einer Textdatei mit 365 Zitaten jeden Tag einen Autor und ein zugehöriges Zitat lädt und dieses mit JOptionPane anzeigt.
Das ganze sieht bei mir folgendermaßen aus:
JOptionPane.showMessageDialog(null, zitateliste.get(tagNum).getZitat()+System.getProperty("line.separator")+"- "+zitateliste.get(tagNum).getAutor(), "Zitat", JOptionPane.PLAIN_MESSAGE);
Er zeigt also in zwei Zeilen einmal das Zitat selbst und in einer weiteren Zeile den Autor an.

zitateliste ist in dem Fall ein Array aus Objekten meiner eigenen Klasse Zitat, welche Eigenschaften wie den Zitatstring und den Autor trägt. Mein Problem ist nun folgendes: Heute habe ich zufällig das längste Zitat der Reihe gehabt, es war insgesamt so breit, dass das Ausgabefenster breiter als mein PC-Bildschirm war.
Ich würde nun gerne erreichen, dass automatisch ein Zeilenumbruch beim nächsten Wort ab einer bestimmten Länge gemacht wird, sodass das Ausgabefenster nicht so extrem breit wird. Da ich die Zitate aus einer Textdatei lese und diese immer mal abgeändert werden können, ist ein manuelles Zeilenumbruch Einfügen keine Alternative.

Hat jemand von euch eine innovativ-raffinierte Idee, wie ich das mit dem automatischen Umbruch erreichen kann?

Vielen Dank und euch schöne Feiertage.

Gruß
Lukas

man kann auf diese Weise das Forum mit Content befüllen und jeweils Stunde(n) auf Antworten warten,
oder etwas Englisch und Suchmaschinen lernen und die Frage in Sekunden selber beantworten :wink:

eine Lösung zumindest:

‚joptionpane line wrap‘
java - Text wrap in JOptionPane? - Stack Overflow

→ HTML nutzen, etwa

    public static void main(String[] args)     {
        String st = "lange Zeile lange Zeile lange Zeile ";
        st = st + st;
        st = st + st;
        st = st + st;
        st = st + st;
        st = "<html><body><p style='width: 200px;'>" + st + "</p></body></html>";
        JOptionPane.showMessageDialog(null, st, "Zitat", JOptionPane.PLAIN_MESSAGE);
    }
}

Hallöle und herzlichen Dank für Deine Antwort.

Ich habs gewusst. Es war ein Fehler mich nicht mehr mit html zu beschäftigen. Habe das früher mal ein bisschen behandelt, bis ich mir gesagt habe, für Darstellungssprachen bin ich nicht gemacht, habe keine Lust da ewig herumzufummeln, bis es gut aussieht. Aber dass ich das dann so noch gebrauchen würde…

Interessanterweise hab ich die von Dir oben genannte Webseite (erstes GoogleSuchergebnis) schon offen gehabt, habe es aber wegen meiner html-Phobie nicht weiter behandelt…

Naja, Schwamm drüber, freuen sich die nächsten Trottel, die es nicht hinkriegen, dass ich ein Deutsches Thema hab anlegen lassen. :smiley:

Vielen Dank und schöne Weihnachten!

Gruß
Lukas

Dies Thema ist jetzt ja wohl gelöst. Anderenfalls hätte ich da eine Methode, die in einen normalen String Umbrüche einfügt, so dass dieser “nach Möglichkeit”*) in Zeilen der gewünschten Länge passt.

*) bis auf Worte, die Länger sind als die gewünschte Breite.

Huch, daran hatte ich jetzt gar nicht gedacht. Ich habe heute ein kürzeres Zitat als die eingestellt Breite dort. Nun wird das Fenster natürlich in Übergröße angezeigt, wobei der Text im Verhältnis dazu sehr gering ist.
Ich kenne mich mit html, css und diesem Kram nicht weit genug aus, gibt es eine Möglichkeit zu sagen, dass die Angabe, meinetwegen 200px die Maximalbreite ist, es aber auch kleiner sein kann?

@Crian Deine Lösung kannste auch gerne schreiben :slight_smile:

Schönes neues Jahr euch allen!

Da das ganze etwas aufwändiger ist und es ja hier nicht gebraucht wird, habe ich darauf verzichtet.

Wie diese Lösung allgemein aussehen könnte, würde mich aber auch interessieren. Irgendwie die String bounds ausrechnen? Das geht meistens schief (selbst wenn man vereinfachend davon ausgeht, dass der String noch kein HTML ist oder enthält…). Insbesondere glaube ich, dass das schwierig in einem Dialog unterzubringen ist (also ohne eigenes g.drawString), den der Benutzer ggf. größer oder kleiner machen kann… (aber das weicht jetzt wohl wirklich zu weit vom Thema ab…)

Ja, statt width einfach max-width verwenden.
Siehe auch hier: CSS Height and Width Dimensions

Hallihallöle,

danke für eure Antworten.
Für mich als weniger Web- und allgemein Grafikaffinen Entwickler ist es natürlich eher ungewöhnlich mit html und css zu arbeiten. Aber ich schau mir das gerne an. :slight_smile: Aber wie dem auch sei, leider scheint es nicht so funktionieren, wie gedacht.

Vorher hatte ich diesen Quellcode:

			zitatStr = "<html><body><p style='width: 400px;'>" + zitatStr + "</p></body></html>";
			JOptionPane.showMessageDialog(null, zitatStr+System.getProperty("line.separator")+"- "+zitateliste.get(tagNum).getAutor(), "Zitat", JOptionPane.PLAIN_MESSAGE);``` 

Das hatte wie gesagt mit der Breiteeinschränkung gut geklappt, nur eben bei kürzeren nicht.

Wenn ich daraus nun max-width mache, sieht es so aus:
```String zitatStr = zitateliste.get(tagNum).getZitat();
			zitatStr = "<html><body><p style='max-width: 400px;'>" + zitatStr + "</p></body></html>";
			JOptionPane.showMessageDialog(null, zitatStr+System.getProperty("line.separator")+"- "+zitateliste.get(tagNum).getAutor(), "Zitat", JOptionPane.PLAIN_MESSAGE);```

Hier scheint aber nun gar nichts mehr zu passieren. Es ist genauso wie wenn ich html ganz weglasse. Kleinere Texte sind wieder in kleineren Fenstern, aber große Texte schränkt er nicht mehr ein.

Ist eigentlich komisch, weil sowohl die von Dir verlinkte Webseite als auch andere Quellen die ich zu rate zog mir bestätigen, dass max-width existieren soll und width ja auch eine erkennbare Funktion erfüllte.

Schöne Grüße
Lukas

tjaja,

eine naheliegende Variante noch von
java - Resize dialog message (JOptionPane) for long sentence with fixed width - Stack Overflow
lose inspiriert, geht genauso mit dem JTextPane und PreferredSize dort, aber ich nehme an du bevorzugst die simplere Variante:

public class Test2 {
    public static void main(String[] args)    throws Exception  {
        String st = "lange Zeile ";
        st = st + st;
        st = st + st;
        st = st + st;
        st = st + st;
        st = st + st;
        JLabel l = new JLabel(st);
        if (l.getPreferredSize().width > 345)
        {
            st = "<html><body><p style='width: 400px;'>" + st + "</p></body></html>";
        }
        JOptionPane.showMessageDialog(null, st, "Zitat", JOptionPane.PLAIN_MESSAGE);
    }
}

ein JLabel genutzt um die Breite des Textes zu bestimmen, etwas teure Komponenente, könnte gecacht werden,
die 345 ist bisher willkürlich gewählt, gewünschte Grenze genau zu testen,

einfach auf String-Länge zu schauen ginge gewiss auch…

edit:

[quote=FranzFerdinand]zitatStr = "<html><body><p style='max-width: 400px;'>" + zitatStr + "</p></body></html>"; JOptionPane.showMessageDialog(null, zitatStr+System.getProperty("line.separator")+"- "+zitateliste.get(tagNum).getAutor(), ..[/quote]
wieso folgt da eigentlich noch Text hinter dem , der Autor?
baue doch erstmal den anzuzeigenden String fertig, und dann das HTML drumherum…

Hallöle,

ich danke Dir für Deine Hilfe. Eigentlich eine recht simple Sache, stimmt. Ich danke vielmals für Deine Unterstützung, so weit ich sehe, funktioniert das nun so.

Das „teuer“ stimmt natürlich schon. Ich habe JLabel nun als globale Variable in der Klasse eingebaut, sodass es jedes mal einfach nur überschrieben und neu ausgerechnet wird und nicht jedes mal neu generiert. Oder ist das eher der falsche Ansatz, was Du mit cachen meintest?

Was Deine Frage angeht:
Die Antwort ist eine Mischung aus gewolltem Vorausdenken und nicht mitgedacht haben meinerseits.
Ich gebe wie gesagt Zitate aus, die so aufgebaut sind:

"Zitat." //Zeilenumbruch
- Autor

Weil die Autoren stets kurze Infos gewesen sind und nur die Zitate lang, hatte ich das um das große Zitat gepackt. Ich hatte natürlich nicht mitgedacht, dass das html-Gedöns ja das gesamte Feld betrifft. Ich danke für diesen Hinweis. :wink:

Schönen Abend
Lukas

stimmt schon mit dem Cache, solange nicht mehrere Threads gleichzeitig darauf zugreifen,

wie gesagt auch einfach String-Länge > 50 oder so als Möglichkeit betrachten,
ein Text ohne Leerzeichen mit vielen ooooooooooooooo mag zwar breiter sein als i i i i i i i i i, aber kommt ja sicher nicht auf jeden Millimeter an

[QUOTE=SlaterB]stimmt schon mit dem Cache, solange nicht mehrere Threads gleichzeitig darauf zugreifen,

wie gesagt auch einfach String-Länge > 50 oder so als Möglichkeit betrachten,
ein Text ohne Leerzeichen mit vielen ooooooooooooooo mag zwar breiter sein als i i i i i i i i i, aber kommt ja sicher nicht auf jeden Millimeter an[/QUOTE]

Hallöle,
ja, greift nur ein Thread drauf zu. Was die String Länge angeht: Sollte ich mich später dazu entscheiden einen String draus zu machen, kann man auch im Zweifel einen längeren “Lorem Ipsum” Beispieltext nehmen und die durchschnittliche Pixelbreite ausrechnen. Aber dazu wird es glaube ich nicht kommen. Das passt schon so.

Schöne Grüße und herzlichsten Dank
Gruß Lukas

Bei meiner Lösung geht es nur darum, schlichten Text (kein HTML) an Leerzeichen umzubrechen, wenn möglich. Hier meine Tests, daran sieht man die Verwendung recht gut:

    public void break001() {
        String text = "Mal sehen was so passiert! ";
        String expected = "Mal sehen was so passiert!";
        int lineLength = 80;
        String actual = Text.addLineBreaks(text, lineLength);
        assertEquals(expected, actual);
    }

    @Test
    public void break002() {
        String text = "Mal sehen was so passiert! ";
        String expected = "Mal sehen was" + LINE_BREAK + "so passiert!";
        int lineLength = 15;
        String actual = Text.addLineBreaks(text, lineLength);
        assertEquals(expected, actual);
    }

    @Test
    public void break003() {
        String text = "Mal sehen was so passiert! ";
        String expected = "Mal" + LINE_BREAK
                + "sehen" + LINE_BREAK
                + "was" + LINE_BREAK
                + "so" + LINE_BREAK
                + "passiert!";
        int lineLength = 5;
        String actual = Text.addLineBreaks(text, lineLength);
        assertEquals(expected, actual);
    }

    @Test
    public void break004() {
        String text = "Mal sehen was so AUSSERGEWÖHNLICHLANGES passiert! ";
        String expected = "Mal" + LINE_BREAK
                + "sehen" + LINE_BREAK
                + "was" + LINE_BREAK
                + "so" + LINE_BREAK
                + "AUSSERGEWÖHNLICHLANGES" + LINE_BREAK
                + "passiert!";
        int lineLength = 5;
        String actual = Text.addLineBreaks(text, lineLength);
        assertEquals(expected, actual);
    }

    @Test
    public void break005() {
        String text = "Mal_sehen_was_so_AUSSERGEWÖHNLICHLANGES_passiert!";
        String expected = text;
        int lineLength = 5;
        String actual = Text.addLineBreaks(text, lineLength);
        assertEquals(expected, actual);
    }

    @Test
    public void break006() {
        String text = "ab cd ef";
        String expected = "ab" + LINE_BREAK + "cd" + LINE_BREAK + "ef";
        int lineLength = 2;
        String actual = Text.addLineBreaks(text, lineLength);
        assertEquals(expected, actual);
    }

    @Test
    public void break007() {
        String text = "ab cd ef";
        String expected = "ab" + LINE_BREAK + "cd" + LINE_BREAK + "ef";
        int lineLength = 1;
        String actual = Text.addLineBreaks(text, lineLength);
        assertEquals(expected, actual);
    }

    @Test
    public void break008() {
        String text = "ab cd ef";
        String expected = "ab" + LINE_BREAK + "cd" + LINE_BREAK + "ef";
        int lineLength = 3;
        String actual = Text.addLineBreaks(text, lineLength);
        assertEquals(expected, actual);
    }

    @Test
    public void break009() {
        String text = "ab cd ef";
        String expected = "ab" + LINE_BREAK + "cd" + LINE_BREAK + "ef";
        int lineLength = 4;
        String actual = Text.addLineBreaks(text, lineLength);
        assertEquals(expected, actual);
    }

    @Test
    public void break010() {
        String text = "ab cd ef";
        String expected = "ab cd" + LINE_BREAK + "ef";
        int lineLength = 5;
        String actual = Text.addLineBreaks(text, lineLength);
        assertEquals(expected, actual);
    }

    @Test
    public void break011() {
        String text = "ab cd ef gh ij kl mn op qr";
        String expected = "ab cd" + LINE_BREAK + "ef gh" + LINE_BREAK + "ij kl"
                + LINE_BREAK + "mn op" + LINE_BREAK + "qr";
        int lineLength = 5;
        String actual = Text.addLineBreaks(text, lineLength);
        assertEquals(expected, actual);
    }

    @Test
    public void break012() {
        String text = "Mal
sehen
was so AUSSERGEWÖHNLICHLANGES passiert! ";
        String expected = "Mal" + LINE_BREAK
                + "sehen" + LINE_BREAK
                + "was so" + LINE_BREAK
                + "AUSSERGEWÖHNLICHLANGES" + LINE_BREAK
                + "passiert!";
        int lineLength = 10;
        String actual = Text.addLineBreaks(text, lineLength);
        assertEquals(expected, actual);
    }

LINE_BREAK ist dabei System.getProperty("line.separator")

Der Code selbst schreit dringend nach Refakturierung, wie man leicht sehen kann (viel zu lang, Kommentare rufen nach Methoden, ekliger Marker an der äußeren Schleife). Also bitte nicht meckern. Vielleicht setze ich mich nochmal daran und mache es schöner.

     * Versieht einen langen Text mit Zeilenumbrüchen, so dass die Zeilen - so überhaupt
     * Leerzeichen vorhanden sind, höchstens lineLength Zeichen lang sind. Hierbei wird dieser
     * zuvor an schon vorhandenen Umbrüchen umgebrochen und nur die Teile dazwischen behandelt.
     *
     * @param text
     *            Gegebener Text
     * @param lineLength
     *            Zeilenlänge
     * @return umgebrochener Text
     */
    public static String addLineBreaks(final String text, final int lineLength) {
        String[] parts = text.trim().split("(" + LINE_BREAK + "|
|
)", -1);
        StringBuilder builder = new StringBuilder();
        for (String part : parts) {
            builder.append(addLineBreaksToParts(part, lineLength));
            builder.append(LINE_BREAK);
        }
        builder.replace(builder.length() - LINE_BREAK.length(),
                builder.length(), "");

        return builder.toString();
    }

    /**
     * Versieht einen langen Text mit Zeilenumbrüchen, so dass die Zeilen - so überhaupt
     * Leerzeichen vorhanden sind, höchstens lineLength Zeichen lang sind.
     *
     * @param text
     *            Gegebener Text
     * @param lineLength
     *            Zeilenlänge
     * @return umgebrochener Text
     */
    private static String addLineBreaksToParts(final String text, final int lineLength) {

        if (text.length() <= lineLength) {
            return text;
        }

        StringBuilder builder = new StringBuilder();

        /* Aktueller Startindex der Zeile: */
        int index = 0;

        int textLength = text.length(); // Abkürzung


        OUTER_LOOP: while (index < textLength - 1) {
            debug("Schleifenstart: index = " + index + ", restlicher Text: "
                    + text.substring(index));
            /*
             * Falls der Rest des Textes kurz genug ist, wird er hinzugefügt und
             * die Schleife verlassen:
             */
            if (textLength - index <= lineLength) {
                debug("Rest kurz genug.");
                append(builder, text.substring(index, textLength));
                break OUTER_LOOP;
            }

            /* Es wird der Index des nächsten Leerzeichens berechnet: */
            int indexOfSpace = text.indexOf(' ', index);
            debug("indexOfSpace = " + indexOfSpace);

            /*
             * Falls es kein nächstes Leerzeichen gibt, wird ebenfalls der Rest
             * hinzugefügt und die Schleife verlassen:
             */
            if (-1 == indexOfSpace) {
                debug("Kein Leerzeichen gefunden.");
                append(builder, text.substring(index, textLength));
                break OUTER_LOOP;
            }

            /*
             * Falls das nächste Leerzeichen schon zu weit weg ist, wird dieser
             * Teil eingefügt:
             */
            if (indexOfSpace - index > lineLength) {
                append(builder, text.substring(index, indexOfSpace));
                index = indexOfSpace + 1;
                continue OUTER_LOOP;
            }

            /* Es wird der Index des übernächsten Leerzeichens berechnet: */
            int indexOfNextSpace = text.indexOf(' ', indexOfSpace+1);
            debug("indexOfNextSpace = " + indexOfNextSpace);

            /*
             * Falls es kein übernächstes Leerzeichen gibt, wird der Teil
             * zwischen den Leerzeichen, ein Zeilenumbruch sowie der Rest
             * hinzugefügt und die Schleife verlassen:
             */
            if (-1 == indexOfNextSpace) {
                debug("Kein nächstes Leerzeichen gefunden.");
                append(builder, text.substring(index, indexOfSpace));
                append(builder, text.substring(indexOfSpace+1));
                /*
                 * Der Text endet nicht auf ein Leerzeichen, da wir vorhin
                 * text.trim() ausgeführt haben. Deshalb müssen nach
                 * indexOfSpace noch Zeichen im Text sein.
                 */
                break OUTER_LOOP;
            }

            /*
             * Wir suchen solange das nächste Leerzeichen, bis der Text zu lang
             * würde:
             */
            while (indexOfNextSpace <= index + lineLength) {
                int rememberIndexOfNextSpace = indexOfNextSpace;
                indexOfNextSpace = text.indexOf(' ', indexOfSpace+1);
                debug("indexOfSpace = " + indexOfSpace
                        + ", indexOfNextSpace = " + indexOfNextSpace
                        + ", rememberIndexOfNextSpace = "
                        + rememberIndexOfNextSpace);

                /*
                 * Falls es kein x-nächstes Leerzeichen gibt, wird der Teil
                 * zwischen den Leerzeichen und ein Zeilenumbruch die zum
                 * nächsten Schleifendurchlauf gewechselt:
                 */
                if (-1 == indexOfNextSpace) {
                    debug("Kein nächstes Leerzeichen in innerer Whilschleife "
                            + "gefunden.");
                    append(builder, text.substring(index, indexOfSpace));
                    index = indexOfSpace + 1;
                    continue OUTER_LOOP;
                }
                else {
                    indexOfSpace = rememberIndexOfNextSpace;
                }
            }

            append(builder, text.substring(index, indexOfSpace));
            index = indexOfSpace + 1;
        }

        if (builder.toString().endsWith(LINE_BREAK)) {
            builder.replace(builder.length() - LINE_BREAK.length(),
                    builder.length(), "");
        }

        return builder.toString();
    }

    /**
     * Fügt einen Textteil gefolgt von einem Zeilenumbruch zum StringBuilder hinzu.
     *
     * @param builder
     *            StringBuilder, dem der Text hinzugefügt werden soll.
     * @param text
     *            Anzufügender Textteil.
     */
    private static void append(final StringBuilder builder, final String text) {
        debug("Füge hinzu: " + text);
        builder.append(text);
        debug("Füge hinzu: Zeilenumbruch");
        builder.append(LINE_BREAK);
    }

    /** Falls das Debugging aktiv ist, wird eine Ausgabe vorgenommen. */
    private static void debug(final String text) {
        if (DEBUG) {
            System.out.println(text);
        }
    }

Ahso, ja, nur anhand des Inhalts. Richtig kompliziert wird das eben, wenn man (für nicht-monospaced-Schriften) die
mmmmmmmmmmmmmmmmm vs.
iiiiiiiiiiiiiiiii
Sache berücksichtigen will (ja, das sind gleich viele). Und wenn noch HTML oder irgendwelche willkürlichen True-Type-Fonts und Unicode-Zeichen dazukommen, kommt wieder §1 zum Tragen: Fonts sind immer Kacke :smiley:

Das JLabel bietet da dann wohl das beste Verhältnis zwischen Aufwand (praktisch 0) und Allgemeingültigkeit (alles, was sie Sun/Oracle-Leute in 20 Jahren zu Fonts eben gemacht haben…). Und auch wenn es „teuer“ ist, ein JLabel zu erstellen, ist das vollkommen vernachlässigbar, wenn man damit nicht die Bounds von 10000 Strings mit 60 FPS ausrechnen will.

Naja, das hatte ich oben ja aber auch gleich geschrieben: “In einen normalen String”

Ich habe mal versucht herumzurefakturieren, aber richtig schön bekomme ich das ganze gerade nicht. Nun sieht es so aus und ich weiß nichtmal, ob das schöner ist:


    /** Systemunabhängiger Zeilenumbruch. */
    private static final String LINE_BREAK = System.getProperty("line.separator");

    /** Gibt an, ob Debugginginformationen ausgegeben werden sollen. */
    private static final boolean DEBUG = false;

    /** Gewünschte Zeilenlänge. */
    private final int lineLength;

    /** Ein Textteil, der gerade umgebrochen werden soll. */
    private String text;

    /** Ein StringBuilder, in dem Textteile und Umbrüche gesammelt werden. */
    private StringBuilder builder;

    /** Aktueller Index im gerade betrachteten Textteil. */
    private int index;

    /** Länge des gerade betrachteten Textteils. */
    private int textLength;

    /**
     * Konstruktor.
     *
     * @param lineLength
     *            Gewünschte Zeilenlänge.
     */
    public TextBreaker(int lineLength) {
        this.lineLength = lineLength;
    }

    /**
     * Versieht einen langen Text mit Zeilenumbrüchen, so dass die Zeilen - so überhaupt
     * Leerzeichen vorhanden sind - höchstens lineLength Zeichen lang sind. Hierbei wird dieser
     * zuvor an schon vorhandenen Umbrüchen umgebrochen und nur die Teile dazwischen behandelt.
     *
     * @param text
     *            Gegebener Text.
     * @return Umgebrochener Text.
     */
    public String addLineBreaks(String text) {
        String[] parts = text.trim().split("(" + LINE_BREAK + "|
|
)", -1);
        StringBuilder builder = new StringBuilder();
        for (String part : parts) {
            builder.append(addLineBreaksToPart(part));
            builder.append(LINE_BREAK);
        }
        builder.replace(builder.length() - LINE_BREAK.length(), builder.length(), "");

        return builder.toString();
    }

    /**
     * Versieht einen langen Text mit Zeilenumbrüchen, so dass die Zeilen - so überhaupt
     * Leerzeichen vorhanden sind, höchstens lineLength Zeichen lang sind.
     *
     * Hier weiß man, dass keine Umbrüche im Text vorkommen.
     *
     * @param text
     *            Gegebener Text.
     * @return Umgebrochener Text.
     */
    private String addLineBreaksToPart(String text) {
        if (text.length() <= lineLength) {
            return text;
        }
        else {
            return addLineBreaksToLongPart(text);
        }
    }

    /**
     * Versieht einen langen Text mit Zeilenumbrüchen, so dass die Zeilen - so überhaupt
     * Leerzeichen vorhanden sind, höchstens lineLength Zeichen lang sind.
     *
     * Hier weiß man, dass keine Umbrüche im Text vorkommen und dass der Textteil zu lang ist.
     *
     * @param text
     *            Gegebener Text.
     * @return Umgebrochener Text.
     */
    private String addLineBreaksToLongPart(String text) {
        this.text = text;
        builder = new StringBuilder();
        index = 0;
        textLength = text.length();

        workOnText();

        if (builder.toString().endsWith(LINE_BREAK)) {
            builder.replace(builder.length() - LINE_BREAK.length(), builder.length(), "");
        }

        return builder.toString();
    }

    private void workOnText() {
        while (index < textLength - 1) {
            debug("Schleifenstart: index = " + index + ", restlicher Text: "
                    + text.substring(index));
            /*
             * Falls der Rest des Textes kurz genug ist, wird er hinzugefügt und die Schleife
             * verlassen:
             */
            if (textLength - index <= lineLength) {
                debug("Rest kurz genug.");
                append(text.substring(index, textLength));
                return;
            }

            /* Es wird der Index des nächsten Leerzeichens berechnet: */
            int indexOfSpace = text.indexOf(' ', index);
            debug("indexOfSpace = " + indexOfSpace);

            /*
             * Falls es kein nächstes Leerzeichen gibt, wird ebenfalls der Rest hinzugefügt und die
             * Schleife verlassen:
             */
            if (-1 == indexOfSpace) {
                debug("Kein Leerzeichen gefunden.");
                append(text.substring(index, textLength));
                return;
            }

            /* Falls das nächste Leerzeichen schon zu weit weg ist, wird dieser Teil eingefügt: */
            if (indexOfSpace - index > lineLength) {
                append(text.substring(index, indexOfSpace));
                index = indexOfSpace + 1;
                continue;
            }

            /* Es wird der Index des übernächsten Leerzeichens berechnet: */
            int indexOfNextSpace = text.indexOf(' ', indexOfSpace+1);
            debug("indexOfNextSpace = " + indexOfNextSpace);

            /*
             * Falls es kein übernächstes Leerzeichen gibt, wird der Teil zwischen den Leerzeichen,
             * ein Zeilenumbruch sowie der Rest hinzugefügt und die Schleife verlassen:
             */
            if (-1 == indexOfNextSpace) {
                debug("Kein nächstes Leerzeichen gefunden.");
                append(text.substring(index, indexOfSpace));
                append(text.substring(indexOfSpace + 1));
                /*
                 * Der Text endet nicht auf ein Leerzeichen, da wir vorhin text.trim() ausgeführt
                 * haben. Deshalb müssen nach indexOfSpace noch Zeichen im Text sein.
                 * Daher kann text.substring(indexOfSpace + 1) keine Probleme machen.
                 */
                /*
                 * Ich traue dem nicht, weil nicht der Part getrimmt wird, sondern nur der
                 * Originalstring. Aber ich finde kein Gegenbeispiel...
                 */
                return;
            }

            /* Wir suchen solange das nächste Leerzeichen, bis der Text zu lang würde: */
            indexOfSpace = searchForSpaces(indexOfSpace, indexOfNextSpace);
            if (-2 == indexOfSpace) {
                continue;
            }

            append(text.substring(index, indexOfSpace));
            index = indexOfSpace + 1;
        }
    }

    /**
     * Sucht so lange nach dem nächste, bis der Text zu lang würde.
     *
     * @param indexOfSpaceStart
     *            Index des ersten Leerzeichens ab dem Index.
     * @param indexOfNextSpaceStart
     *            Index des zweiten Leerzeichens ab dem Index.
     * @return Index des Leerzeichens, an dem getrennt werden soll, oder -2 als Zeichen dafür, dass
     *         schon angefügt wurde und die äußere Schleife weiterlaufen soll.
     */
    private int searchForSpaces(int indexOfSpaceStart, int indexOfNextSpaceStart) {
        int indexOfSpace = indexOfSpaceStart;
        int indexOfNextSpace = indexOfNextSpaceStart;

        while (indexOfNextSpace <= index + lineLength) {
            int rememberIndexOfNextSpace = indexOfNextSpace;
            indexOfNextSpace = text.indexOf(' ', indexOfSpace+1);
            debug("indexOfSpace = " + indexOfSpace + ", indexOfNextSpace = " + indexOfNextSpace
                    + ", rememberIndexOfNextSpace = " + rememberIndexOfNextSpace);

            /*
             * Falls es kein x-nächstes Leerzeichen gibt, wird der Teil zwischen den
             * Leerzeichen und ein Zeilenumbruch die zum nächsten Schleifendurchlauf
             * gewechselt:
             */
            if (-1 == indexOfNextSpace) {
                debug("Kein nächstes Leerzeichen in innerer Whileschleife gefunden.");
                append(text.substring(index, indexOfSpace));
                index = indexOfSpace + 1;
                return -2;
            }
            else {
                indexOfSpace = rememberIndexOfNextSpace;
            }
        }

        return indexOfSpace;
    }

    /**
     * Fügt einen Textteil gefolgt von einem Zeilenumbruch zum StringBuilder hinzu.
     *
     * @param textPart
     *            Anzufügender Textteil.
     */
    private void append(final String textPart) {
        debug("Füge hinzu: " + textPart);
        builder.append(textPart);
        debug("Füge hinzu: Zeilenumbruch");
        builder.append(LINE_BREAK);
    }

    /** Falls das Debugging aktiv ist, wird eine Ausgabe vorgenommen. */
    private static void debug(final String text) {
        if (DEBUG) {
            System.out.println(text);
        }
    }

}

Mit den Tests


import static org.junit.Assert.assertEquals;

import org.junit.Test;

public class TextBreakerTest {

    @Test
    public void breakNoBreak() {
        String text = "Mal sehen was so passiert!";
        String expected = text;
        int lineLength = 80;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakNoBreakRemoveTrailingBlank() {
        String text = "Mal sehen was so passiert! ";
        String expected = "Mal sehen was so passiert!";
        int lineLength = 80;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakAddOneBreak() {
        String text = "Mal sehen was so passiert! ";
        String expected = "Mal sehen was" + LINE_BREAK + "so passiert!";
        int lineLength = 15;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakAddFourBreaks() {
        String text = "Mal sehen was so passiert! ";
        String expected = "Mal" + LINE_BREAK
                + "sehen" + LINE_BREAK
                + "was" + LINE_BREAK
                + "so" + LINE_BREAK
                + "passiert!";
        int lineLength = 5;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakAddBreaksWithLongParts() {
        String text = "Mal sehen was so AUSSERGEWÖHNLICHLANGES passiert! ";
        String expected = "Mal" + LINE_BREAK
                + "sehen" + LINE_BREAK
                + "was" + LINE_BREAK
                + "so" + LINE_BREAK
                + "AUSSERGEWÖHNLICHLANGES" + LINE_BREAK
                + "passiert!";
        int lineLength = 5;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakAddNoBreaksAtUnderscores() {
        String text = "Mal_sehen_was_so_AUSSERGEWÖHNLICHLANGES_passiert!";
        String expected = text;
        int lineLength = 5;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakBreakAtExaktTextPartLengths() {
        String text = "ab cd ef";
        String expected = "ab" + LINE_BREAK + "cd" + LINE_BREAK + "ef";
        int lineLength = 2;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakBreakAtSmallerLineLengthThanTextPartLength() {
        String text = "ab cd ef";
        String expected = "ab" + LINE_BREAK + "cd" + LINE_BREAK + "ef";
        int lineLength = 1;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakBreakAtLineLengthEqualsTextPartLengthAndSpace() {
        String text = "ab cd ef";
        String expected = "ab" + LINE_BREAK + "cd" + LINE_BREAK + "ef";
        int lineLength = 3;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakBeforeLineIsTooLong() {
        String text = "ab cd ef";
        String expected = "ab" + LINE_BREAK + "cd" + LINE_BREAK + "ef";
        int lineLength = 4;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakAfterTwoParts() {
        String text = "ab cd ef";
        String expected = "ab cd" + LINE_BREAK + "ef";
        int lineLength = 5;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakSmallParts() {
        String text = "ab cd ef gh ij kl mn op qr";
        String expected = "ab cd" + LINE_BREAK + "ef gh" + LINE_BREAK + "ij kl"
                + LINE_BREAK + "mn op" + LINE_BREAK + "qr";
        int lineLength = 5;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakHandleExistingBreakes() {
        String text = "Mal
sehen
was so AUSSERGEWÖHNLICHLANGES passiert! ";
        String expected = "Mal" + LINE_BREAK
                + "sehen" + LINE_BREAK
                + "was so" + LINE_BREAK
                + "AUSSERGEWÖHNLICHLANGES" + LINE_BREAK
                + "passiert!";
        int lineLength = 10;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakExistingBreaksWithSpaces() {
        String text = "ab" + LINE_BREAK + " " + LINE_BREAK + "cd";
        String expected = "ab" + LINE_BREAK + " " + LINE_BREAK + "cd";
        int lineLength = 2;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakExistingBreaksWithSpaces2() {
        String text = "ab ab " + LINE_BREAK + " " + LINE_BREAK + "cd";
        String expected = "ab" + LINE_BREAK + "ab " + LINE_BREAK + " " + LINE_BREAK + "cd";
        int lineLength = 3;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakExistingBreaksWithSpaces3() {
        String text = "ab ab" + LINE_BREAK + " " + LINE_BREAK + "cd";
        String expected = "ab" + LINE_BREAK + "ab" + LINE_BREAK + " " + LINE_BREAK + "cd";
        int lineLength = 3;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakExistingBreaksWithSpaces4() {
        String text = "ab x" + LINE_BREAK + " " + LINE_BREAK + "cd";
        String expected = "ab" + LINE_BREAK + "x" + LINE_BREAK + " " + LINE_BREAK + "cd";
        int lineLength = 3;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakExistingBreaksWithSpaces5() {
        String text = "ab " + LINE_BREAK;
        String expected = "ab";
        int lineLength = 3;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

}

*** Edit ***

Wie oft, wenn einem etwas gar nicht gefällt, sollte man den Algorithmus ändern. Die neue Version arbeitet ganz anders und gefällt mir deutlich besser. Dank Rekursion und Entheddern von Aufteilen und StringBuilder wird das ganze wesentlich übersichtlicher:

import java.util.Arrays;
import java.util.List;

public class TextBreaker {

    /** Systemunabhängiger Zeilenumbruch. */
    static final String LINE_BREAK = System.getProperty("line.separator");

    /** Gewünschte Zeilenlänge. */
    private final int lineLength;

    /**
     * Konstruktor.
     *
     * @param lineLength
     *            Gewünschte Zeilenlänge.
     */
    public TextBreaker(int lineLength) {
        this.lineLength = lineLength;
    }

    /**
     * Versieht einen langen Text an Leerzeichen mit Zeilenumbrüchen, so dass die Zeilen höchstens
     * lineLength Zeichen lang sind, - falls ausreichend Leerzeichen dafür vorhanden sind.
     *
     * Hierbei wird der Text zuvor an schon vorhandenen Umbrüchen umgebrochen.
     *
     * @param text
     *            Gegebener Text.
     * @return Umgebrochener Text.
     */
    public String addLineBreaks(String text) {
        List<String> brokenParts = new ArrayList<>();

        for (String part : breakTextBetweenExistingLineBreaks(text)) {
            brokenParts.addAll(breakAtSpaces(part));
        }

        return generateOutputString(brokenParts);
    }

    private List<String> breakTextBetweenExistingLineBreaks(String text) {
        return Arrays.asList(text.split("(" + LINE_BREAK + "|
|
)", -1));
    }

    private String generateOutputString(List<String> brokenParts) {
        StringBuilder builder = new StringBuilder();

        boolean first = true;
        for (String brokenPart : brokenParts) {
            if (first) {
                first = false;
            }
            else {
                builder.append(LINE_BREAK);
            }
            builder.append(brokenPart);
        }

        return builder.toString();
    }

    private List<String> breakAtSpaces(String part) {
        List<String> brokenParts = new ArrayList<>();

        if (textIsSmallEnough(part) || textHasNoSpaces(part)) {
            brokenParts.add(part);
        }
        else {
            brokenParts.addAll(breakAtBestSpace(part));
        }

        return brokenParts;
    }

    private List<String> breakAtBestSpace(String part) {
        List<String> brokenParts = new ArrayList<>();

        int devidingIndex;

        int firstSpaceIndex = part.indexOf(" ");

        if (textIsSmallEnoughAtSpaceIndex(firstSpaceIndex)) {
            int lastGoodSpaceIndex = firstSpaceIndex;
            int aSpaceIndex = part.indexOf(" ", lastGoodSpaceIndex + 1);
            while (aSpaceIndex > -1 && textIsSmallEnoughAtSpaceIndex(aSpaceIndex)) {
                lastGoodSpaceIndex = aSpaceIndex;
                aSpaceIndex = part.indexOf(" ", lastGoodSpaceIndex + 1);
            }
            devidingIndex = lastGoodSpaceIndex;
        }
        else {
            devidingIndex = firstSpaceIndex;
        }

        String partBeforeSpace = part.substring(0, devidingIndex);
        String partAfterSpace = part.substring(devidingIndex + 1);
        brokenParts.add(partBeforeSpace);
        brokenParts.addAll(breakAtSpaces(partAfterSpace));

        return brokenParts;
    }

    private boolean textIsSmallEnough(String part) {
        return part.length() <= lineLength;
    }

    private boolean textIsSmallEnoughAtSpaceIndex(int spaceIndex) {
        return spaceIndex <= lineLength;
        /*
         * Nicht
         *     spaceIndex -1 <= lineLength
         * weil das Leerzeichen ja nicht mitzählt!
         */
    }

    private boolean textHasNoSpaces(String text) {
        return !text.contains(" ");
    }

}

Mit den Tests

import static ...paket....TextBreaker.LINE_BREAK;

import org.junit.Test;

public class TextBreakerTest {

    @Test
    public void breakNoBreak() {
        String text = "Mal sehen was so passiert!";
        String expected = text;
        int lineLength = 80;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakNoBreakNotRemovingTrailingBlank() {
        String text = "Mal sehen was so passiert! ";
        String expected = "Mal sehen was so passiert! ";
        int lineLength = 80;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakAddOneBreak() {
        String text = "Mal sehen was so passiert!";
        String expected = "Mal sehen was" + LINE_BREAK + "so passiert!";
        int lineLength = 15;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakAddFourBreaks() {
        String text = "Mal sehen was so passiert!";
        String expected = "Mal" + LINE_BREAK
                + "sehen" + LINE_BREAK
                + "was" + LINE_BREAK
                + "so" + LINE_BREAK
                + "passiert!";
        int lineLength = 5;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakAddBreaksWithLongParts() {
        String text = "Mal sehen was so AUSSERGEWÖHNLICHLANGES passiert!";
        String expected = "Mal" + LINE_BREAK
                + "sehen" + LINE_BREAK
                + "was" + LINE_BREAK
                + "so" + LINE_BREAK
                + "AUSSERGEWÖHNLICHLANGES" + LINE_BREAK
                + "passiert!";
        int lineLength = 5;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakAddNoBreaksAtUnderscores() {
        String text = "Mal_sehen_was_so_AUSSERGEWÖHNLICHLANGES_passiert!";
        String expected = text;
        int lineLength = 5;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakBreakAtExaktTextPartLengths() {
        String text = "ab cd ef";
        String expected = "ab" + LINE_BREAK + "cd" + LINE_BREAK + "ef";
        int lineLength = 2;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakBreakAtSmallerLineLengthThanTextPartLength() {
        String text = "ab cd ef";
        String expected = "ab" + LINE_BREAK + "cd" + LINE_BREAK + "ef";
        int lineLength = 1;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakBreakAtLineLengthEqualsTextPartLengthAndSpace() {
        String text = "ab cd ef";
        String expected = "ab" + LINE_BREAK + "cd" + LINE_BREAK + "ef";
        int lineLength = 3;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakBeforeLineIsTooLong() {
        String text = "ab cd ef";
        String expected = "ab" + LINE_BREAK + "cd" + LINE_BREAK + "ef";
        int lineLength = 4;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakAfterTwoParts() {
        String text = "ab cd ef";
        String expected = "ab cd" + LINE_BREAK + "ef";
        int lineLength = 5;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakSmallParts() {
        String text = "ab cd ef gh ij kl mn op qr";
        String expected = "ab cd" + LINE_BREAK + "ef gh" + LINE_BREAK + "ij kl"
                + LINE_BREAK + "mn op" + LINE_BREAK + "qr";
        int lineLength = 5;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakHandleExistingBreakes() {
        String text = "Mal
sehen
was so AUSSERGEWÖHNLICHLANGES passiert!";
        String expected = "Mal" + LINE_BREAK
                + "sehen" + LINE_BREAK
                + "was so" + LINE_BREAK
                + "AUSSERGEWÖHNLICHLANGES" + LINE_BREAK
                + "passiert!";
        int lineLength = 10;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakExistingBreaksWithSpaces() {
        String text = "ab" + LINE_BREAK + " " + LINE_BREAK + "cd";
        String expected = "ab" + LINE_BREAK + " " + LINE_BREAK + "cd";
        int lineLength = 2;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakExistingBreaksWithSpaces2() {
        String text = "ab ab " + LINE_BREAK + " " + LINE_BREAK + "cd";
        String expected = "ab" + LINE_BREAK + "ab " + LINE_BREAK + " " + LINE_BREAK + "cd";
        int lineLength = 3;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakExistingBreaksWithSpaces3() {
        String text = "ab ab" + LINE_BREAK + " " + LINE_BREAK + "cd";
        String expected = "ab" + LINE_BREAK + "ab" + LINE_BREAK + " " + LINE_BREAK + "cd";
        int lineLength = 3;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakExistingBreaksWithSpaces4() {
        String text = "ab x" + LINE_BREAK + " " + LINE_BREAK + "cd";
        String expected = "ab" + LINE_BREAK + "x" + LINE_BREAK + " " + LINE_BREAK + "cd";
        int lineLength = 3;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void breakExistingBreaksWithSpaces5() {
        String text = "ab " + LINE_BREAK;
        String expected = "ab " + LINE_BREAK;
        int lineLength = 3;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void multipleSpaces1() {
        String text = "ab   cd";
        String expected = "ab " + LINE_BREAK + " cd";
        int lineLength = 3;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

    @Test
    public void multipleSpaces2() {
        String text = "ab   cd";
        String expected = "ab" + LINE_BREAK + " "+ LINE_BREAK + "cd";
        int lineLength = 2;
        TextBreaker breaker = new TextBreaker(lineLength);
        String actual = breaker.addLineBreaks(text);
        assertEquals(expected, actual);
    }

}

Ohne es in Detail nachvollzogen zu haben (ich hatte auch irgendwann mal sowas hingehackt, aber nicht im Sinne von etwas, was “öffentlich verwendbar sein sollte”, aber daher erinnere ich mich noch an) einige potentielle Stolperfallen. Z.B. die Frage, was mit einem “abc_______def” (_=Leerzeichen) passieren soll, wenn die maximale Zeilenlänge 2, 8 oder 12 sein soll - also allgemein die Frage: Wie geht man mit mehreren Leerzeichen um. Verallgemeinerbar auf Tabs " ", und natürlich (besonders interessant) newlines " ".

Ansonsten ist der einfachste Algorithmus ja greedy. Also, ein Text wie xxxx x xx xxxxxxx würde bei einer Breite von 7 mit dem greedy-Ansatz ja in


xxxx x 
xx
xxxxxxx

umgebrochen, wobei


xxxx 
x xx
xxxxxxx

da “schöner” wäre. Man kann das ganze dann als “kürzeste-Wege-Problem” auffassen, wobei der Graph aus den Knoten=Leerzeichen, Kanten=Umbruchstellen und Kantengewichten=“Häßlichkeit des Umbruchs” bestehen würde. (Das driftet jetzt immer weiter ab… )