Reputation: 549
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
Upvotes: 17
Views: 25564
Reputation: 2549
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;
};
Upvotes: 0
Reputation: 1599
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.
Upvotes: 1
Reputation: 17
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>
Upvotes: 0
Reputation: 2992
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", "");
}
Upvotes: 16
Reputation: 31
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.
Upvotes: 3
Reputation: 549
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
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)
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>
Upvotes: 1
Reputation: 66389
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)
Upvotes: 17