JSFiddle - React, Tailwind, and code Playground

by disfated

HTML

<div id="result"></div>
<textarea>
The whole of the paragraph is being set in lines of constant width, with a tolerance of 1 (of which more later.) The text being considered commences with the unadorned text on the left. Down the left hand side is all of the text in which no feasible line breaks were found. At the right, at the end of each of the runs of unbroken text, are those elements which are feasibe break-points. The text is shown with all of the possible hypenation points marked with a middle dot, as is commonly used in dictionaries. Only in "lime-tree" is there a manifest hyphen. As the algorithm starts, a so-called active node, representing the beginning of the text, has been introduced to the otherwise empty list of active nodes. The algorithm now works through the paragraph, considering each possible break-point. Possible break-points, in English text, are the spaces between the words, explicit hyphens and possible hyphenation points, represented in some implementation-dependent way.

What is indicated in the diagram is that no feasible break-points were found in the text starting "In olden times when wish·ing still helped one, there lived". How is this determined? For each possible break-point, for each active node, the width of the text from the node to the possible break-point is calculated. If this length "fits" in the available line width, this is a feasible break-point.

The precise meaning of "fits" will be discussed later, but for now simply assume that there is some elasticity in the definition of "fits". One of the limits of this elasticity is defined by the tolerance. No break-points are available in the intial run of text because the amount of "stretching" required to fit the text on a line by itself would exceed the tolerance. At the words "a" and "king" in "a king" however, feasible break-points are found.

Some measure of the goodness, or correspondingly, the badness, of a given break-point is required. This measure is related to the...

CSS

textarea {
   width: 100%;
   box-sizing: border-box;
   min-height: 100px;
   padding: 5px;
   border: 1px solid #999;
   sizing: vertical;
   display: block;
}

#result {
   width: 100%;
   box-sizing: border-box;
   padding: 5px;
   border: 2px solid #EEE;
   display: block;
}

JavaScript

$(document).ready(function() {
    $('button').click(function() {
        var text = $('textarea').val();
        var wrapped = wordwrap(text, {stop: 80});
        $('$result').html(wrapped);
    });
});

function wordwrap(text, options) {
    var start = options.start || 0;
    var stop = options.stop || 80;
    var mode = options.mode || 'soft';
    var re = mode === 'hard' ? /\b/ : /(\S+\s+)/;
    var chunks = text.toString()
        .split(re)
        .reduce(function (acc, x) {
            if (mode === 'hard') {
                for (var i = 0; i < x.length; i += stop - start) {
                    acc.push(x.slice(i, i + stop - start));
                };
            } else {
                acc.push(x);
            };
            return acc;
        }, []);
    
    return chunks.reduce(function (lines, rawChunk) {
        if (rawChunk === '') return lines;
        
        var chunk = rawChunk.replace(/\t/g, '    ');
        
        var i = lines.length - 1;
        if (lines[i].length + chunk.length > stop) {
            lines[i] = lines[i].replace(/\s+$/, '');
            
            chunk.split(/\n/).forEach(function (c) {
                lines.push(
                    new Array(start + 1).join(' ')
                    + c.replace(/^\s+/, '')
                );
            });
        }
        else if (chunk.match(/\n/)) {
            var xs = chunk.split(/\n/);
            lines[i] += xs.shift();
            xs.forEach(function (c) {
                lines.push(
                    new Array(start + 1).join(' ')
                    + c.replace(/^\s+/, '')
                );
            });
        }
        else {
            lines[i] += chunk;
        }
        
        return lines;
    }, [ new Array(start + 1).join(' ') ]).join('\n');
};