


I have the following code on my server that takes a string and inserts newlines in such a way that the string is separated into lines, all of which are shorter than maxLineLength in characters. This ensures that the text, when printed, will fit within a certain width.

const formatTextWrap = (text, maxLineLength) => {
  var words = text.replace(/[\r\n]+/g, ' ').split(' ')
  var lineLength = 0
  var output = ''
  for (var word of words) {
    if (lineLength + word.length >= maxLineLength) {
      output += `\n${word} `
      lineLength = word.length + 1
    } else {
      output += `${word} `
      lineLength += word.length + 1
    }
  }
  return output
}



I would suggest .split(/\s+/) for the const words to allow multiple spaces to act as one split. This is for those warped individuals who wrongly believe their typing instructor from 1980 should govern how they space after punctuation in the 21st Century. –
Alan Mimms
Aug 10, 2020 at 23:41


~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


I had occasion to write a word wrap function recently, and I want to share what I came up with.

I used a TDD approach almost as strict as the one from the Go example. I started with the test that wrapping the string "Hello, world!" at 80 width should return "Hello, World!". Clearly, the simplest thing that works is to return the input string untouched. Starting from that, I made more and more complex tests and ended up with a recursive solution that (at least for my purposes) quite efficiently handles the task.

Pseudocode for the recursive solution:

Function WordWrap (inputString, width)
    Trim the input string of leading and trailing spaces.

    If the trimmed string's length is <= the width,
        Return the trimmed string.
    Else,
        Find the index of the last space in the trimmed string, starting at width

        If there are no spaces, use the width as the index.

        Split the trimmed string into two pieces at the index.

        Trim trailing spaces from the portion before the index,
        and leading spaces from the portion after the index.

        Concatenate and return:
          the trimmed portion before the index,
          a line break,
          and the result of calling WordWrap on the trimmed portion after
            the index (with the same width as the original call).

This only wraps at spaces, and if you want to wrap a string that already contains line breaks, you need to split it at the line breaks, send each piece to this function and then reassemble the string. Even so, in VB.NET running on a fast machine, this can handle about 20 MB/second.

This algoritihm is quite good and seemingly one of the few properly handling words longer than a single line. To clarify wording, "Find the index of the last space" means to look for spaces in front of width, going backwards in the string. In case you have unproportional fonts, it's possible to measure the line from the start there, breaking when it exceeds the width and recording the last space index while doing so.

+++++++++++++++++++++++

Ich hatte kürzlich die Gelegenheit, eine Wortumbruch-Funktion zu schreiben, und ich möchte mit Ihnen teilen, was ich mir ausgedacht habe.

Ich habe einen TDD-Ansatz verwendet, der fast so streng ist wie der aus dem Go-Beispiel. Ich begann mit dem Test, dass das Umbrechen der Zeichenkette "Hallo, Welt!" mit einer Breite von 80 "Hallo, Welt!" zurückgeben sollte. Es ist klar, dass es am einfachsten ist, die Eingabezeichenkette unverändert zurückzugeben. Davon ausgehend habe ich immer komplexere Tests durchgeführt und bin schließlich zu einer rekursiven Lösung gekommen, die (zumindest für meine Zwecke) die Aufgabe recht effizient löst.

Pseudocode für die rekursive Lösung:

Funktion WordWrap (inputString, width)
    Schneidet die Eingabezeichenkette von führenden und nachgestellten Leerzeichen ab.

    Wenn die Länge der gekürzten Zeichenkette <= der Breite ist,
        Rückgabe der gekürzten Zeichenkette.
    Andernfalls,
        Finde den Index des letzten Leerzeichens in der gekürzten Zeichenkette, beginnend bei width

        Wenn es keine Leerzeichen gibt, wird die Breite als Index verwendet.

        Teilen Sie die gekürzte Zeichenfolge am Index in zwei Teile auf.

        Schneiden Sie nachfolgende Leerzeichen aus dem Teil vor dem Index ab,
        und führende Leerzeichen aus dem Teil nach dem Index.

        Konkatenieren und zurückgeben:
          den abgeschnittenen Teil vor dem Index,
          einen Zeilenumbruch,
          und das Ergebnis des Aufrufs von WordWrap für den beschnittenen Teil nach
            dem Index (mit der gleichen Breite wie beim ursprünglichen Aufruf).

Wenn Sie eine Zeichenkette umbrechen wollen, die bereits Zeilenumbrüche enthält, müssen Sie sie an den Zeilenumbrüchen aufteilen, jeden Teil an diese Funktion senden und die Zeichenkette dann wieder zusammensetzen. Trotzdem kann diese Funktion in VB.NET auf einem schnellen Rechner etwa 20 MB/Sekunde verarbeiten.

Dieser Algorithmus ist recht gut und scheint einer der wenigen zu sein, der Wörter, die länger als eine Zeile sind, richtig verarbeitet. Um den Wortlaut zu verdeutlichen: "Finde den Index des letzten Leerzeichens" bedeutet, dass nach Leerzeichen vor der Breite gesucht wird, indem in der Zeichenkette rückwärts gegangen wird. Bei unproportionalen Schriftarten ist es möglich, die Zeile vom Anfang an zu messen, sie zu unterbrechen, wenn sie die Breite überschreitet, und dabei den Index des letzten Leerzeichens zu notieren.


2

I don't know of any specific algorithms, but the following could be a rough outline of how it should work:

    For the current text size, font, display size, window size, margins, etc., determine how many characters can fit on a line (if fixed-type), or how many pixels can fit on a line (if not fixed-type).
    Go through the line character by character, calculating how many characters or pixels have been recorded since the beginning of the line.
    When you go over the maximum characters/pixels for the line, move back to the last space/punctuation mark, and move all text to the next line.
    Repeat until you go through all text in the document.

In .NET, word wrapping functionality is built into controls like TextBox. I am sure that a similar built-in functionality exists for other languages as well.

-----

With or without hyphenation?

Without it's easy. Just encapsulate your text as wordobjects per word and give them a method getWidth(). Then start at the first word adding up the rowlength until it is greater than the available space. If so, wrap the last word and start counting again for the next row starting with this one, etc.

With hyphenation you need hyphenation rules in a common format like: hy-phen-a-tion

Then it's the same as the above except you need to split the last word which has caused the overflow.

A good example and tutorial of how to structure your code for an excellent text editor is given in the Gang of Four Design Patterns book. It's one of the main samples on which they show the patterns.

+++++++++++++

Ich kenne keine spezifischen Algorithmen, aber das Folgende könnte eine grobe Skizze sein, wie es funktionieren sollte:

    Ermitteln Sie für die aktuelle Textgröße, Schriftart, Anzeigegröße, Fenstergröße, Ränder usw., wie viele Zeichen in eine Zeile passen (bei fester Schrift) bzw. wie viele Pixel in eine Zeile passen (bei nicht fester Schrift).
    Gehen Sie die Zeile Zeichen für Zeichen durch und berechnen Sie, wie viele Zeichen oder Pixel seit Beginn der Zeile aufgezeichnet wurden.
    Wenn Sie die maximale Anzahl von Zeichen/Pixeln für die Zeile überschritten haben, gehen Sie zum letzten Leerzeichen/Satzzeichen zurück und verschieben den gesamten Text in die nächste Zeile.


    Wiederholen Sie diesen Vorgang, bis Sie den gesamten Text des Dokuments durchlaufen haben.

In .NET ist die Funktion des Zeilenumbruchs in Steuerelemente wie TextBox integriert. Ich bin sicher, dass eine ähnliche Funktion auch in anderen Sprachen vorhanden ist.

-----

Mit oder ohne Silbentrennung?

Ohne ist es ganz einfach. Kapseln Sie einfach Ihren Text als wordobjects pro Wort und geben Sie ihnen eine Methode getWidth(). Dann fangen Sie beim ersten Wort an und addieren die Zeilenlänge, bis sie größer ist als der verfügbare Platz. Wenn dies der Fall ist, umbrechen Sie das letzte Wort und beginnen Sie mit der Zählung für die nächste Zeile, die mit diesem Wort beginnt, usw.

Bei der Silbentrennung benötigen Sie Regeln für die Silbentrennung in einem gängigen Format wie: hy-phen-a-tion

Dann ist es dasselbe wie oben, nur dass Sie das letzte Wort, das den Überlauf verursacht hat, trennen müssen.

Ein gutes Beispiel und eine Anleitung, wie Sie Ihren Code für einen ausgezeichneten Texteditor strukturieren können, finden Sie in dem Buch Gang of Four Design Patterns. Es ist eines der Hauptbeispiele, an dem die Muster gezeigt werden.


##################

Paint.DrawRichText(something) ist eine der Fähigkeiten von Paint. Es ist eine Methode, die es beherrscht. Sie benötigt mindestens drei Dinge in Klammern. Es kann auch ein paar mehr brauchen. Hier haben wir vier "Argumente", oder "Dinge in Klammern". Erstes Element: was gedruckt werden soll. Zweites Element: Wie weit in der Breite soll gedruckt werden. Dritter Punkt: Wie weit unten soll der Druck beginnen. Der 960er gibt einen Zoll Rand vor. 96 Punkte pro Zoll ist eine typische Standardauflösung für Drucker. Die Zahl ist "Zehntel eines Punktes". (Ich hoffe, ich habe das richtig verstanden.) Vierter Punkt: Wie breit soll mein Druck sein? Antwort: Die gesamte Breite, die Paint zulässt, abzüglich eines Zolls links und eines Zolls rechts. Jeder Zoll ist 960. Ziehen Sie zwei davon ab.
