relyt
relyt

Reputation: 679

Limit number of characters per line and wrap while preserving leading white space

My goal is:

I'm able to limit the number of characters per line correctly, but I'm having trouble with the white space, etc...

Any help would be appreciated

Fiddle

var str = `i am a string that has new lines and whitespace. I need to preserve the leading whitespace and add it back on after the string has been broken up after n characters.

     This line has leading whitespace. Tttttt rrrrrr  
     ttgvgggjjj. Gyjfry bh jkkfrtuj hhdt iihdrtttg.
     Here is another line. Hjkkl gggdetu jcfgjbfftt.
This line has no leading whitespace, so i dont need any reapplied. Jjjxsrg bjlkdetyhk llhfftt`;

function addNewlines(str) { 
    var result = ''; 
    while(str.length > 0) { 
          result += str.substring(0, 25) + '<br />'; 
          str = str.substring(25); 
    } 

    return result; 
}

var newStr = addNewlines(str).toString();
document.getElementById("result").innerHTML = newStr;

Should end up looking something like this:

i am a string that has ne
w lines and whitespace. I
need to preserve the lea
ding whitespace and add i
t back on after the strin
g has been broken up afte
r n characters. 

   This line has leading
   whitespace. Tttttt rr
   rrrr ttgvgggjjj. Gyjf
   ry bh jkkfrtuj hhdt i
   ihdrtttg. Here is ano
   ther line. Hjkkl gggd
   etu jcfgjbfftt. 

This line has no leading
 whitespace, so i dont n
 eed any reapplied. Jjjx
 srg bjlkdetyhk llhfftt

Upvotes: 1

Views: 693

Answers (2)

Jorjon
Jorjon

Reputation: 5434

Sometimes when dealing with a new algorithm, is much easier to use two or more passes. So you think how it would work in steps instead of everything at once.

I have an implementation working that have 2 passes: first I join the paragraph lines, and in the second pass I perform the actual splitting.

I'm sure there's a better approach, but this works and is fully commented.

I wasn't sure your version of ES so I made it ES5-compatible.

https://jsfiddle.net/2ngtj3aj/

// Divides a string into chunks of specific size
function chunk(str, size) {
  var chunks = [];
  while(str) { 
    chunks.push(str.substring(0, size));
    str = str.substring(size);
  }
  return chunks;
}

// Removes all spaces from the left of a string
function trimLeft(str) {
  while(str.substr(0,1) == " ") {
    str = str.substr(1);
  }
  return str;
}

// Repeats a character n times
function repeat(c, n) {
  return Array(n + 1).join(c);
}

function addNewlines(str) { 
    var MAX_COLS = 25; // maximum colums on the text
    var DEFAULT_LEADING = 3; // default leading to reapply
    var MIN_LEADING = 1; // minimum amount of spacing to be considered a paragraph
    var CR = "\n";
    var result = '';
    var leading = 0;
    var chunks = [];
    var formattedLines = []; // store the intermediary lines
    var startLeadingSpaceLine = -1; // where does a paragrph start
    var i, l; // counters
    var lines = str.split(CR); // input lines
    
    // In the first pass, we join the paragraph lines
    
    for (i = 0; i < lines.length; i++) {
    
      l = lines[i];
      // If line is empty, we don't use it
      if (l.trim() == "") continue;

      if (l.substr(0, MIN_LEADING) == repeat(" ", MIN_LEADING)) {
        // If line has leading whitespace, remove the leading space
        l = trimLeft(l);
        if (startLeadingSpaceLine > -1) {
          // If we are already on a paragraph,
          // we don't overwrite the flag
        } else {
          // But if this is the first line of an paragraph,
          // We set a flag to allow to join this line with the next one
          // if that contains identation as well
          startLeadingSpaceLine = i;
        }
        // If we are on a paragraph, we don't add this line to the array,
        // first we need to wait to see if we have more lines in the paragraph
        // We also update the line in the array with the whitespace removed
        lines[i] = l;
        continue;
      } else {
        // If line doesn't has whitespace, we check if we have just finished
        // an paragraph
        if (startLeadingSpaceLine > -1) {
          // If we do, then we need to add the previous lines to the array
          // Note: if we want to leave a space between lines, we need to use
          // join(' ') instead of join('')
          var paragraphLines = lines.slice(startLeadingSpaceLine, i).join('');
          // We add the whitespace we like
          paragraphLines = repeat(" ", DEFAULT_LEADING) + paragraphLines;
          formattedLines.push(paragraphLines);
        }
        
      }
      formattedLines.push(l);
    }
    
    // Now we parse again the lines, this time we will divide
    // the lines into chunks
    
    for (i = 0; i < formattedLines.length; i++) {
    
      l = formattedLines[i];

      // Now check against DEFAULT_LEADAING since we have already changed
      // the identation
      if (l.substr(0, DEFAULT_LEADING) == repeat(" ", DEFAULT_LEADING)) {
        
        // If line has leading whitespace, remove the leading space
        // We aded it before just to be able to detect the paragraph.
        l = trimLeft(l);
        
        // Divide the line into chunks. We take into account the space
        // we have removed, otherwise the paragraph will bleed to the
        // right.
        l = chunk(l, MAX_COLS - DEFAULT_LEADING);
        
        // We add leading space to all paragraph lines
        for(var j = 0; j < l.length; j++) {
          l[j] = repeat(" ", DEFAULT_LEADING) + l[j];
        }
        
        // Optional: we add blank lines between paragraphs
        l = [" "].concat(l).concat([" "]);
        
      } else {
      
        // If we have a simple line, just divide it into chunks
        l = chunk(l, MAX_COLS);

			}
      
      // Join the lines with newlines and add to the result
      l = l.join(CR);
      result += l + CR;
    }
    
    
    return result; 
}

var process = function() {
	var newStr = addNewlines(input.value).toString();
	document.getElementById("result").innerHTML = newStr;
}

var input = document.getElementById("input");
input.addEventListener("change", process);
input.addEventListener("keyup", process);
process();
<h3>RESULTS</h3>

<textarea id="input" rows="10" cols="80">i am a string that has new lines and whitespace. I need to preserve the leading whitespace and add it back on after the string has been broken up after n characters.

     This line has leading whitespace. Tttttt rrrrrr  
     ttgvgggjjj. Gyjfry bh jkkfrtuj hhdt iihdrtttg.
     Here is another line. Hjkkl gggdetu jcfgjbfftt.
This line has no leading whitespace, so i dont need any reapplied. Jjjxsrg bjlkdetyhk llhfftt</textarea>
<pre id="result"></pre>

Upvotes: 1

pascalpuetz
pascalpuetz

Reputation: 5418

General Logic

Your string already contains all whitespaces. If you add console.log(newStr) to your script and look into your console, you'll see that the whitespaces are already there.

You might want to remove all trailing whitespaces (whitespaces before a new line character starts). You can do that by using replace with a regex: var newStr = addNewlines(str).toString().replace(/\s+(?=\n)/g, "");.

Additionally, since all Tab-Characters ("\t") will be recognized as only 1 character but take up way more space than the others, you might want to replace those with 3 or 4 spaces instead. Something like .replace(/\t/g, " ")

Another thing to take into consideration are newlines that are already present before. You'll want to stop counting there and start a new counter after the already present newline.

Displaying inside a Textarea

var str = `i am a string that has new lines and whitespace. I need to preserve the leading whitespace and add it back on after the string has been broken up after n characters.

			 This line has leading whitespace. Tttttt rrrrrr  
			 ttgvgggjjj. Gyjfry bh jkkfrtuj hhdt iihdrtttg.
			 Here is another line. Hjkkl gggdetu jcfgjbfftt.
		This line has no leading whitespace, so i dont need any reapplied. Jjjxsrg bjlkdetyhk llhfftt`;

function addNewlines(str) { 
  var result = ''; 
  str = str.replace(/\t/g, "   ");
  
  while(str.length > 0) { 
      nPos = str.indexOf("\n");
      len = nPos > 0 && nPos < 25 ? nPos + 1 : 25;

      result += str.substring(0, len) + '\n'; 
      str = str.substring(len); 
  } 

  return result; 
}


var newStr = addNewlines(str).toString().replace(/\s+(?=\n)/g, "");
document.getElementById("result").value = newStr;
<textarea id="result"></textarea>

Displaying in HTML

If you want to display those whitespaces in HTML, then you can make use of the CSS Property white-space: pre.

var str = `i am a string that has new lines and whitespace. I need to preserve the leading whitespace and add it back on after the string has been broken up after n characters.

			 This line has leading whitespace. Tttttt rrrrrr  
			 ttgvgggjjj. Gyjfry bh jkkfrtuj hhdt iihdrtttg.
			 Here is another line. Hjkkl gggdetu jcfgjbfftt.
		This line has no leading whitespace, so i dont need any reapplied. Jjjxsrg bjlkdetyhk llhfftt`;

		function addNewlines(str) { 
			var result = ''; 
      str = str.replace(/\t/g, "   ");
			while(str.length > 0) { 
          nPos = str.indexOf("<br />");
          len = nPos > 0 && nPos < 25 ? nPos + 1 : 25;
          
				  result += str.substring(0, len) + '\n'; 
				  str = str.substring(len); 
			} 

			return result; 
		}
    
    
		var newStr = addNewlines(str).toString().replace(/\s+(?=\n)/g, "");
		console.log(newStr);
		document.getElementById("result1").innerHTML = newStr;
		document.getElementById("result2").innerHTML = newStr;
		document.getElementById("result3").innerHTML = newStr;
		document.getElementById("result4").innerHTML = newStr;
		document.getElementById("result5").innerHTML = newStr;
div {

font-family: monospace;
}
<h1>normal</h1>
	<div id="result1" style="white-space: normal"></div>
    <h1>pre</h1>
	<div id="result2" style="white-space: pre"></div>
    <h1>nowrap</h1>
	<div id="result3" style="white-space: nowrap"></div>
    <h1>pre-wrap</h1>
	<div id="result4" style="white-space: pre-wrap"></div>
    <h1>pre-line</h1>
	<div id="result5" style="white-space: pre-line"></div>

Also, in your example you're using the tab character to indent your lines. If you wanted those removed as well, then you'd have to remove all occurences of those. You can do that by using another regex and the replace method like this: var newStr = addNewlines(str).toString().replace(/\s+(?=\n)/g, "").replace(/\t/g, "");.

Upvotes: 0

Related Questions