finding "line-breaks" in textarea that is word-wrapping ARABIC text
Asked Answered
C

7

17

I have a string of text that I display in a textarea (right-to-left orientation). The user can resize the textarea dynamically (I use jquery for this) and the text will wrap as necessary.

When the user hits submit, I will take that text and create an image using PHP, BUT before submitting I would like to know where the "line-breaks" or rather "word-wraps" occur.

Everywhere I have looked so far only shows me how to process line-breaks on the php side. I want to make it clear that there ARE NO LINE-BREAKS. What I have is one LONG string that will be word-wrapped in different ways based on the width of the textarea set by the user.

I can't use "columns" or any other standard width representation because I have a very complex arabic font that is actually composed of glyphs (characters) of numerous different widths.

If anyone knows of a way of accessing where the word wraps occur (either in a textarea or a div if need-be), I'd really like to know.

My only other solution is to actually store (in my DB) the width of every single character (somewhat tedious since there are over 200 characters in 600 different fonts, for a total of...some huge number).

My hopes aren't high, but I thought I would ask.

Thanks

i. jamal

Cressler answered 18/1, 2011 at 1:48 Comment(1)
How do you generate the image? Usually when drawing text on image you can define rectangle wrapping the text and it will be wrapped according to that rectangle.Monastery
M
17

Well, instead of finding the line breaks (which is virtually impossible) you can force them into the textarea, using this function:

function ApplyLineBreaks(strTextAreaId) {
    var oTextarea = document.getElementById(strTextAreaId);
    if (oTextarea.wrap) {
        oTextarea.setAttribute("wrap", "off");
    }
    else {
        oTextarea.setAttribute("wrap", "off");
        var newArea = oTextarea.cloneNode(true);
        newArea.value = oTextarea.value;
        oTextarea.parentNode.replaceChild(newArea, oTextarea);
        oTextarea = newArea;
    }

    var strRawValue = oTextarea.value;
    oTextarea.value = "";
    var nEmptyWidth = oTextarea.scrollWidth;
    var nLastWrappingIndex = -1;
    for (var i = 0; i < strRawValue.length; i++) {
        var curChar = strRawValue.charAt(i);
        if (curChar == ' ' || curChar == '-' || curChar == '+')
            nLastWrappingIndex = i;
        oTextarea.value += curChar;
        if (oTextarea.scrollWidth > nEmptyWidth) {
            var buffer = "";
            if (nLastWrappingIndex >= 0) {
                for (var j = nLastWrappingIndex + 1; j < i; j++)
                    buffer += strRawValue.charAt(j);
                nLastWrappingIndex = -1;
            }
            buffer += curChar;
            oTextarea.value = oTextarea.value.substr(0, oTextarea.value.length - buffer.length);
            oTextarea.value += "\n" + buffer;
        }
    }
    oTextarea.setAttribute("wrap", "");
}

This function get ID of textarea and whenever there is word wrap, it push new line break into the textarea. Run the function in the form submit and you will get the text with proper line breaks in the server side code.

Tested successfully for IE, Chrome and Firefox feel free to see for yourself here: http://jsfiddle.net/yahavbr/pH79a/1/ (The preview will show the new lines)

Monastery answered 18/1, 2011 at 9:23 Comment(3)
lol...The code you provided uses the same brilliant idea I had (well done you) BUT when I test it, it breaks the first line correctly then adds a line break after every character (tested on the link jsfiddle.net)Cressler
@Irfan works just fine for me... type "hello world how are you today? Hope you're fine!" and it will split nicely to three lines. Can you post screenshot?Monastery
This function can take several seconds to run on long blocks of text. I have created a faster version below that uses binary search.Hent
H
16

Here is a functionally-equivalent implementation of Shadow Wizard's solution that is much faster because it uses binary search instead of linear search to determine the length of each line:

function ApplyLineBreaks(strTextAreaId) {
    var oTextarea = document.getElementById(strTextAreaId);
    if (oTextarea.wrap) {
        oTextarea.setAttribute("wrap", "off");
    }
    else {
        oTextarea.setAttribute("wrap", "off");
        var newArea = oTextarea.cloneNode(true);
        newArea.value = oTextarea.value;
        oTextarea.parentNode.replaceChild(newArea, oTextarea);
        oTextarea = newArea;
    }

    var strRawValue = oTextarea.value;
    oTextarea.value = "";
    var nEmptyWidth = oTextarea.scrollWidth;

    function testBreak(strTest) {
        oTextarea.value = strTest;
        return oTextarea.scrollWidth > nEmptyWidth;
    }
    function findNextBreakLength(strSource, nLeft, nRight) {
        var nCurrent;
        if(typeof(nLeft) == 'undefined') {
            nLeft = 0;
            nRight = -1;
            nCurrent = 64;
        }
        else {
            if (nRight == -1)
                nCurrent = nLeft * 2;
            else if (nRight - nLeft <= 1)
                return Math.max(2, nRight);
            else
                nCurrent = nLeft + (nRight - nLeft) / 2;
        }
        var strTest = strSource.substr(0, nCurrent);
        var bLonger = testBreak(strTest);
        if(bLonger)
            nRight = nCurrent;
        else
        {
            if(nCurrent >= strSource.length)
                return null;
            nLeft = nCurrent;
        }
        return findNextBreakLength(strSource, nLeft, nRight);
    }

    var i = 0, j;
    var strNewValue = "";
    while (i < strRawValue.length) {
        var breakOffset = findNextBreakLength(strRawValue.substr(i));
        if (breakOffset === null) {
            strNewValue += strRawValue.substr(i);
            break;
        }
        var nLineLength = breakOffset - 1;
        for (j = nLineLength - 1; j >= 0; j--) {
            var curChar = strRawValue.charAt(i + j);
            if (curChar == ' ' || curChar == '-' || curChar == '+') {
                nLineLength = j + 1;
                break;
            }
        }
        strNewValue += strRawValue.substr(i, nLineLength) + "\n";
        i += nLineLength;
    }
    oTextarea.value = strNewValue;
    oTextarea.setAttribute("wrap", "");
}

Updated fiddle.

Hent answered 2/11, 2013 at 15:40 Comment(3)
Amazing, I took the liberty to add a live test case and tested with 10 paragraphs of Lorem Ipsum and the change is really big indeed. Kudos!Monastery
Note that this doesn't work in some cases. If the textarea width is just as big as the end of a word, the word will appear in the next line.Mayotte
this is good and while it seems to handle spaces correctly, it doesn't break the line at the end of the spaces, for example: "Lorem ipsum dolor , sed do incididunt ut"Roundhouse
T
3

I think the easiest way of doing it would be to set the word wrap of your textarea in the html code to 'hard', like this:

<textarea id='yourTextArea' wrap='hard'></textarea>

It means that wherever your textarea breaks the line for you will also insert a \n linebreak charachter in the string when submitting. If you then find this going through your string it will make it easy to determine where was the break originally. Or you can also turn these characters to
html tags with the nl2br() function of PHP.

Tenebrae answered 15/11, 2011 at 13:5 Comment(2)
This is very useful. But it seems impossible to detect these line breaks just with javascript without posting them to a server. If anyone finds out how, please post.Kristy
I've tested it's not working as documented in MDN about wrap=hard, Does anybody know how to make use of this promising attribute? codepen.io/Unicornist/pen/xxRVrpW?editors=1010Anhydrite
C
1

For some reason, I was never alerted when this post was updated...and last night, I had this BRILLIANT idea on how to determine where the line breaks were... I would rebuild the string, and check the width each time, and it WORKED so I came here to share it...and found I was 1 week behind

Anyway 2 important things

  1. The code you provided uses the same brilliant idea I had (well done you) BUT when I test it, it breaks the first line correctly then adds a line break after every character (tested on the link jsfiddle.net)

  2. I've added my code which uses jquery and uses the width of a span to determine when to break At first I tried using the width of the div, but div.width() returns the default width, not the width of the content.

I AM AWARE THIS MAY NOT WORK ON ALL BROWSERS so, I ask kindly that if anyone knows of a way of making this foolproof, or close to it, please share.

First, the styles are necessary to synchornize fonts (all attributes) between the textarea and div, set the size, and (for IE) remove any scrollbars that automatically appear.


    .inputArea {
      width:200px; 
      height:100px; 
      font-family:Arial; 
      font-size:12px; 
      overflow: auto; 
      border: 1px solid #cccccc;
      padding:0;
      margin:0;
    }
    .divArea {
      font-family:Arial; 
      font-size:12px;
    }
  

Next, I include jquery and my custom functions:

  
  <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js
"></script>
  <script type="text/javascript">
  $(document).ready(function() {
     $("#breakUp").click(function () {
       showLineBreaks(); 
       addLineBreaks(); 
     });

     function showLineBreaks() {
       content = $("#textEntered").val();
       //replace line breaks in content with "|" to allow for replacement below
       content = content.replace("\r\n", "
"); content = content.replace("\r", "
"); content = content.replace("\n", "
"); $("#unedited").html(content); } function addLineBreaks() { content = $("#textEntered").val(); //replace line breaks in content with "|" to allow for replacement below content = content.replace("\r\n", "|"); content = content.replace("\r", "|"); content = content.replace("\n", "|"); tempContent = ""; $("#edited").html(""); for (var i = 0; i "); } else { tempContent = $("#edited").html(); $("#edited").html(tempContent + content.charAt(i)); if ($("#edited").width() > 200) { $("#edited").html(tempContent + "
" + content.charAt(i)); } } } } }); <script>

And finally, my html test page


  Enter text into the textarea below (Set to 200 px width, 100 px height)<br>
  <textarea id="textEntered" class="inputArea"></textarea>
  <br><br>
  The div below will display that text WITHOUT wrapping, BUT replacing all existing line breaks with <br><br>
  <div id="unedited"></div>
  <br>
  The following div will display that text with line breaks ADDED to fit the wrapping<br>
  <div class="divArea"><span id="edited"></span></div>  
  <br>
  <button id="breakUp">Click Here to Convert</button>
Cressler answered 27/1, 2011 at 16:52 Comment(0)
R
1

Here is my example for computing the actual number of rows (after line wrapping) for every line of text in a textarea. Take note that the text width of a textarea shrinks slightly when the scrollbar starts to appear. This can cause further wrapping of the previous content so that the previously computed row height will not be correct. Therefore, the textarea must have CSS-style overflow-y set to "scroll" to force the display of the scrollbar all the time.

function get_row_wraps(txtArea){                        
    if(wrap=="off"){                                    
        var out=[];                                     
        for(var i=txtArea.split_lines.length; i>=0; --i)
            out[i]=1;                                   
        return out;                                     
    }                                                   

    var its=txtArea.value.split("\n");                        
    var newArea = txtArea.cloneNode(true);              
    newArea.hidden=true;                                
    newArea.style.visibility = "hidden";                
    txtArea.parentNode.appendChild(newArea);            

    // get single row height                            
    newArea.style.height="auto";                        
    newArea.style.overflowY="scroll";                   
    newArea.value="1\n2\n3";                            
    var unit_height=newArea.scrollHeight;               
    newArea.value="1\n2\n3\n4";                         
    var unit_height=newArea.scrollHeight-unit_height;   
    newArea.style.height=Math.round(unit_height*1.5)+"px"; // so that the scrollbar does not vanish
    newArea.value="";                                   

    // obtain row height for every line of text         
    function comp_Nrow(scroll_height){                  
        return Math.floor(scroll_height/unit_height);   
    }                                                   
    function calc_rows(txt){                            
        newArea.value+=txt;                             
        return comp_Nrow(newArea.scrollHeight);         
    }                                                   
    var out=[];                                         
    for(var i=0; i<its.length; i++)                     
        out.push(calc_rows(i==0?its[i]:("\n"+its[i]))); 
    txtArea.parentNode.removeChild(newArea);            
    for(var i=out.length-1; i>0; i--)                   
        out[i]-=out[i-1];                               
//  alert(out);                                         
    return out;                                         
}                                                       

The above function returns the actual number of wrapped rows for each line of text (separated by "\n") in an textarea. The computation is accurate at least for Chrome and Firefox.

Ralline answered 9/1, 2016 at 3:38 Comment(0)
G
0

Code tested at "Crome", "Firefox" and "IE". Get line feeds and carriage returns a component "textArea" (client side with javascript).

WORKS WELL!. I would like to share it with you

important to include the style

    <html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<%--//definimos un estilo--%>
 <STYLE type="text/css">
    #CadTemp 
    {
        font-family: "Arial", serif; 
        font-size: 12pt; 
        visibility: hidden;
        position: absolute;
        top: -100;
        left: 0px;
    }
 </STYLE>

    <script type="text/javascript">
        function mostrar() {
            return 'ancho cadena: ' + document.getElementById('myTextarea').value.visualLength() + '  \n' + 'ancho textarea: ' + (document.getElementById('myTextarea').scrollWidth -4);
        }


        //sustituimos el espacio en blanco por el punto, tienen exactamente el mismo tamaño en 'pixeles'
        function reemplazarEspacios(texto) {
            var devolver = "";
            for (var i = 0; i < texto.length; i++) {
                if (texto.charAt(i) == ' ') {
                    devolver += '.'
                } else {
                    devolver += texto.charAt(i);
                }
            }
            return devolver;
        }

        // Calcula los pixeles de ancho que ocupa un texto (la cadena debe tener el mismo tamaño y tipo de fuente)
        String.prototype.visualLength = function () {
            var ruler = document.getElementById("CadTemp");            
            ruler.innerHTML = reemplazarEspacios(this)
            return ruler.offsetWidth;
        }

        //quitar espacios a la derecha de la cadena
        String.prototype.rtrim = function() {return this.replace(/\s+$/,"");}

        //devuelve el ultimo espacio de la cadena (que no sea espacio final)
        function IndEspacio(cadena) {
            //quito los espacios al final
            var cadenaTemp = cadena.rtrim();
            return cadenaTemp.lastIndexOf(' ');
        }

        //insertar un salto de linea
        function AplicarSaltosLinea(ID_elemento) {
           //guardo el elemento web en una variable
           var TextArea = document.getElementById(ID_elemento);
           var cadenaTexto = "";
           var Cadenafinal = "";
           var buffer = "";

           //recorremos toda la cadena
           for (var i = 0; i < TextArea.value.length; i++) {
                //guardamos el caracater en la cadena
                cadenaTexto += TextArea.value.charAt(i);

                //si hay un retorno de carro, antes de llegar al final del textArea
                if (TextArea.value.charAt(i) == '\n') {
                    Cadenafinal += cadenaTexto.substr(0, cadenaTexto.lastIndexOf('\n') + 1) ;
                    cadenaTexto = "";
                }

                //si el ancho actual de la cadena  es mayor o igual que el ancho del textarea (medida pixeles)
                if (cadenaTexto.visualLength() > TextArea.scrollWidth - 4) {
                    //recuperamos el ultimo espacio en blanco antes de la ultima letra o palabra
                    var indiceEspacio = IndEspacio(cadenaTexto)

                    buffer = "";
                    //ultimo espacio en blanco detectado, metemos el trozo de palabra desde el ultimo espacio
                    if (indiceEspacio >= 0) {
                        for (var j = indiceEspacio + 1; j <= i; j++)
                            buffer += cadenaTexto.charAt(j);
                        indiceEspacio = -1;
                    } else {
                        buffer += TextArea.value.charAt(i);
                    }
                    //coloca la cadena 
                    Cadenafinal += cadenaTexto.substr(0, cadenaTexto.length - buffer.length) + "\n";
                    cadenaTexto = buffer;
                }
            }

            Cadenafinal += cadenaTexto;

            document.getElementById("pnlPreview").innerHTML = Cadenafinal.replace(new RegExp("\\n", "g"), "<br />");
        }


    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <span id="CadTemp">hola</span>
    </div>
    <br />
    <div>
        <textarea cols="20" rows="5" id="myTextarea" wrap="hard" 
            style="font-family: Arial, Helvetica, sans-serif; font-size: 12pt"></textarea>
    </div>
    <div id="pnlPreview"></div>
    <div>
        <button type="button" onclick="AplicarSaltosLinea('myTextarea');">Apply Line Breaks</button>
        <button type="button" onclick="alert( document.getElementById('myTextarea').value )">mensaje</button>
        <button type="button" onclick="alert( mostrar())">calcular Ancho Pixel</button>
        <br />
        </div>
    </form>
</body>
</html>
Garland answered 4/11, 2013 at 8:55 Comment(0)
S
0

I don't mean to dig up old posts, but this is the one where I came out after searching for some time. @Shadow Wizard 's solution didn't work for me because it put a space behind every sentence, so I decided to make some adjustments. Instead of doing it per-character, I do it per-word. This results in way easier checks and is also 10 times faster, checking with full Lorem Ipsum (~600ms vs 6 full seconds on fiddle, 120ms without fiddle).

I have created a (documented) fiddle. I'm going to see if I can optimise it a bit more, though.

function applyLineBreaks = function(strTextAreaId) {

    // Get txtarea
    var oTextarea = document.getElementById(strTextAreaId);

    // Disable textarea wrap
    oTextarea.setAttribute("wrap", "off");

    // Split the characters into an array
    var aWords = oTextarea.value.split(' ');

    // Empty the textarea
    oTextarea.value = "";

    // Get textarea scrollwidth
    var nEmptyWidth = oTextarea.scrollWidth;

    // Start looping over the words
    for(var i = 0; i < aWords.length; i++)
    {
        if(i > 1000)
        {
            break;
        }
        var curWord = aWords[i] + ' ';

        // Add character to textarea
        oTextarea.value += curWord;

        // console.log(oTextarea.scrollWidth, nEmptyWidth);
        if(oTextarea.scrollWidth > nEmptyWidth)
        {
            let oldVal      = oTextarea.value;
            let newVal      = oldVal.substring(0, (oldVal.length - (curWord.length + 1))) + "\n" + curWord;
            oTextarea.value = newVal;
        }
    }
    oTextarea.setAttribute("wrap", "");

    return oTextarea.value;
};
Subphylum answered 10/4, 2019 at 13:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.