textarea - how to count wrapped lines/rows - javascript

I need some function That will count rows ( i know that on stackoverflow there are more than hundreds of these questions) but in my case i need to count them even when there is no end of line (mean "/n") because typical function is
textarea.value.substr(0, textarea.selectionStart).split("\n").length;.
It means that if user overflows max length of the row but he doesn't use "enter" and the text is on "new line".
Well, i dont know how to describe it better, so there is a example on fiddle https://jsfiddle.net/fNPvf/12872/
try to write long sentence with no spaces, enters etc.. and you will see where the problem is
what i really don't want is css rule nowrap, overflow-x etc..

Here you go.
/** #type {HTMLTextAreaElement} */
var _buffer;
/**
* Returns the number of lines in a textarea, including wrapped lines.
*
* __NOTE__:
* [textarea] should have an integer line height to avoid rounding errors.
*/
function countLines(textarea) {
if (_buffer == null) {
_buffer = document.createElement('textarea');
_buffer.style.border = 'none';
_buffer.style.height = '0';
_buffer.style.overflow = 'hidden';
_buffer.style.padding = '0';
_buffer.style.position = 'absolute';
_buffer.style.left = '0';
_buffer.style.top = '0';
_buffer.style.zIndex = '-1';
document.body.appendChild(_buffer);
}
var cs = window.getComputedStyle(textarea);
var pl = parseInt(cs.paddingLeft);
var pr = parseInt(cs.paddingRight);
var lh = parseInt(cs.lineHeight);
// [cs.lineHeight] may return 'normal', which means line height = font size.
if (isNaN(lh)) lh = parseInt(cs.fontSize);
// Copy content width.
_buffer.style.width = (textarea.clientWidth - pl - pr) + 'px';
// Copy text properties.
_buffer.style.font = cs.font;
_buffer.style.letterSpacing = cs.letterSpacing;
_buffer.style.whiteSpace = cs.whiteSpace;
_buffer.style.wordBreak = cs.wordBreak;
_buffer.style.wordSpacing = cs.wordSpacing;
_buffer.style.wordWrap = cs.wordWrap;
// Copy value.
_buffer.value = textarea.value;
var result = Math.floor(_buffer.scrollHeight / lh);
if (result == 0) result = 1;
return result;
}
Demo here

If I'm understanding the problem correctly, you need to count two things.
The number of hard line breaks "\n".
The number of lines that wrap past X characters.
Here's some pseudo code:
var lineLengthLimit = 40;
var lineCounter = 0;
foreach(lines as line){
lineCounter+=Math.floor(line.length/lineLengthLimit);
}
lineCounter += lines.length;
Another option might be what this guy suggested: https://stackoverflow.com/a/3697249/1207539 but it seems a bit sketchy.

Related

PaperJs Add 2 raster as 2 symbols in the same project

I have this project in paperjs:
var url = "http://www.clker.com/cliparts/q/I/s/P/E/3/yellow-umbrella-md.png";
raster = new Raster(url);
raster.rotate(10);
raster.scale(0.4);
var url2 = "https://images.vexels.com/media/users/3/145373/isolated/preview/98721f602aa3fadb040e0a161ab3f966-waterdrop-vislumbrante-vis-o-ilustra--o-by-vexels.png";
secondRaster = new Raster(url);
secondRaster.scale(0.9);
var count = 150;
var symbol = new Symbol(raster);
var secondSymbol = new Symbol(secondRaster);
for (var i = 0; i < count; i++) {
// The center position is a random point in the view:
var center = Point.random() * view.size;
var placedSymbol = symbol.place(center);
placedSymbol.scale(i / count);
}
function onFrame(event) {
// Run through the active layer's children list and change
// the position of the placed symbols:
for (var i = 0; i < count; i++) {
var item = project.activeLayer.children[i];
// Move the item 1/20th of its width to the right. This way
// larger circles move faster than smaller circles:
item.position.y += item.bounds.width / 80;
// If the item has left the view on the right, move it back
// to the left:
if (item.bounds.bottom > view.size.width) {
item.position.y = -item.bounds.width;
}
}
}
The first raster has a symbol works good, but the second can't make it work... I read about to add more than one symbol to project.activeLayer.children but don't work. Even if I do a group of an array with both symbols also don't show up.
I read in a post that symbols can't be added as a group. Being that be true, it should be ok to be added even though isolated...
Anybody had done something similar?
Thank you
There are some mistakes in your code:
The most important one, that make you think that the second raster doesn't work, is that you are creating the second raster with the variable url instead of url2. So both rasters use the same image as source...
You need to place the second symbol like you do with the first one otherwise it will never get rendered.
When iterating through active layer children, make sure to iterate over all children by using project.activeLayer.children.length (as you are placing count * 2 symbols).
When checking for bottom reaching items, use height instead of width.
Here is a sketch demonstrating the solution.
var COUNT = 10;
var raster = new Raster('http://www.clker.com/cliparts/q/I/s/P/E/3/yellow-umbrella-md.png');
raster.rotate(10);
raster.scale(0.4);
var secondRaster = new Raster('https://images.vexels.com/media/users/3/145373/isolated/preview/98721f602aa3fadb040e0a161ab3f966-waterdrop-vislumbrante-vis-o-ilustra--o-by-vexels.png');
secondRaster.scale(0.15);
var symbol = new Symbol(raster);
var secondSymbol = new Symbol(secondRaster);
for (var i = 1; i <= COUNT; i++) {
// first symbol
symbol.place(Point.random() * view.size).scale(i / COUNT);
// second symbol
secondSymbol.place(Point.random() * view.size).scale(i / COUNT);
}
function onFrame(event) {
for (var i = 0; i < project.activeLayer.children.length; i++) {
var item = project.activeLayer.children[i];
item.position.y += item.bounds.height / 80;
if (item.bounds.bottom > view.size.height) {
item.position.y = -item.bounds.height;
}
}
}

Adding style tags around specific characters only

I am trying to add inline styling to only numbers in paragraph elements. For example:
<p>This paragraph has the numbers 1 and 2 in it.</p>
So in this instance, I would want to put <span class="style">1</span>and <span class="style">2</span>. Around the two numbers in that paragraph.
I am trying to write a javascript to accomplish this so I don't have to go back into the document I'm working on and manually add the styling tags around each number, as the document is very long.
So far this is what I wrote, but I'm having difficulty figuring out what to do for the next step on how to incorporate the edits back into the paragraph HTML.
let regEx=/[0-9]/g;
let list = [];
let paragraphs = document.getElementsByTagName("p");
for (var i = 0; i < paragraphs.length; i++) {
let html = paragraphs[i].innerHTML;
list.push(html);
}
// all paragraphs into one string.
let joined = list.join(' ');
// all the numbers in the paragraphs stored in array
let numbers = joined.match(regEx);
// define array for styling edits
let edits = [];
// adding the styling tags to each num
numbers.forEach(function(num){
edits.push('<span class="style">' + num + '</span>');
// outputs ["<span class='style'>3</span>", "<span class='style'>7</span>", "<span class='style'>4</span>", "<span class='style'>5</span>"]
});
// need to insert edits into paragraph html
If anyone can offer any suggestions on how I might be able to accomplish this that would be great, I am still relatively new to working with JS.
const paragraphs = document.getElementsByTagName("p");
for (var i = 0; i < paragraphs.length; i++) {
const regEx=/([0-9])/g;
const newHtml = paragraphs[i].innerHTML.replace(regEx, '<span class="style">$1</span>');
paragraphs[i].innerHTML = newHtml;
}
I updated your regex to put the number in a group, then in the string replace you can reference that group, since there is only one it will be $1. As you can see in the replace we are wrapping that with the appropriate span and then plugging it right back into the innerHTML.
I did notice that your regex is only capturing single digit numbers, if you wanted to capture multi-digit numbers, you could update your reg ex like this: /([0-9]+)/g.
I created a simple jsfiddle to show you how it works: https://jsfiddle.net/andyorahoske/dd6k6ekp/35/
I broke out the most fundamental part of this into a reusable function that you may find helpful in other contexts.
/**
* Wraps numbers in a string with any provided wrapper.
* #param {String} str A string containing numbers to be wrapped.
* #param {String} wrapper A string with placeholder %s to define the wrapper. Example - <pre>%s</pre>
* #return {String} The original string with numbers wrapped using the wrapper param.
*/
function wrapNumbers(str, wrapper) {
var numbersInStr = str.match(/\d+/g) || [];
var chunks = [];
var segmentStart = 0;
for(var i = 0; i < numbersInStr.length; i += 1) {
var number = numbersInStr[i];
var indexOfNumber = str.indexOf(number);
var fWrapper = wrapper.replace('%s', number);
chunks.push(str.slice(segmentStart, indexOfNumber));
chunks.push(fWrapper);
segmentStart = indexOfNumber + number.length;
}
if(segmentStart < str.length) {
chunks.push(str.slice(segmentStart, str.length));
}
return chunks.join('');
}
To use this in your use case it might look like the following:
var paragraphs = document.getElementsByTagName("p");
var wrapper = '<span class="style">%s</span>';
for(var i = 0; i < paragraphs.length; i += 1) {
var paragraph = paragraphs[i];
paragraph.innerHTML = wrapNumbers(paragraph.innerHTML, wrapper);
}
Codepen: https://codepen.io/bryceewatson/pen/vRqeVy?editors=1111
Here's a new code https://jsfiddle.net/fazanaka/au4jufrr/1/
var element = document.getElementById('text'),
text = element.innerText,
wordsArray = text.split(' '),
newString;
for(var i = 0; i < wordsArray.length; i++){
if(!isNaN(parseFloat(wordsArray[i])) && isFinite(wordsArray[i])){
wordsArray[i] = "<span class='style'>" + wordsArray[i] + "</span>";
}
}
newString = wordsArray.join(' ');
element.innerHTML = newString;
I hope it helps you
UPD:
For all paragraphs https://jsfiddle.net/fazanaka/qx2ehym4/

Textarea limit characters and lines with Javascript

There are some similar topics to this theme, but current requirements are more specific and I couldn't combine it yet with existent solutions, so asking community to help me with. I need to build textarea js validator for rules:
4 lines of 32 characters maximum
of 128 characters maximum
a remaining characters counter is displayed under the textarea
and other that I already found how to do...
Could you please tell how I could implement this?
p.s. It should be clear Javascript solution
This is how your code should look like (see also JSFiddle):
var rows = 4;
var cols = 32;
var body = document.body;
var ta = createElem(body, 'textarea');
var div = createElem(body, 'div');
ta.rows = rows;
ta.cols = cols;
ta.addEventListener('keyup', () => {
var lines = ta.value.split('\n');
lines = lines
.slice(0, Math.min(lines.length, rows))
.map(line => line.substring(0, cols));
ta.value = lines.join('\n');
displayRemaining(cols * rows - ta.value.length + rows - 1);
});
displayRemaining(cols * rows);
function createElem(a, b){
b = document.createElement(b);
a.appendChild(b);
return b;
}
function displayRemaining(a){
div.innerHTML = 'Characters remaining: ' + a + '.';
}

Adobe Edge how to use correctly if / else statements in game?

I'm building a game currently but I have a small problem with keeping the score. Basically I have an textfield that gets a random word input, then if someone clicks on the field or symbol containing the field then I want to check the random word input, if it's a correct word I want to update score textfield, if it's incorrect I want to update errors textfield.
For this I am using an if/else construction, the problem I have with it is that in my game every click only goes either in the if statement or if I change code then only the else, but it's not checking symbol for symbol, every time I click to see if it's an correct word or not. Here is the code I am using on the symbol.click symbol. My question is, am I doing anything wrong in the if/else statements or are my variable calling methods wrong. I have source files on request.
symbol.click:
var y = sym.getVariable("lijst");
var x = "bommen";
// if variables are a match then update score with 1
if (sym.getVariable("x") == sym.getVariable("y"))
{var score3 = sym.getComposition().getStage().getVariable("score1");
score3= score3 +=1;
sym.getComposition().getStage().$ ("scoreTxt").html(score3);
sym.getComposition().getStage().setVariable("score1", score3);
}
// else update error textfield with 1
else {
var fouten= sym.getComposition().getStage().getVariable("fouten1");
fouten= fouten +=1;
sym.getComposition().getStage().$ ("hpTxt").html(fouten);
sym.getComposition().getStage().setVariable("fouten1", fouten);
}
symbol.creationComplete
var words = ['bommen',
'dammen',
'kanonnen',
'dollen',
'bomen',
'feesten',
'lampen',
'voeten',
];
var lijst = words[Math.floor(Math.random() * words.length)];
sym.$("dynamicText").html(lijst);
//And my stage:
stage.creationComplete
// some different variables declarations
sym.setVariable("score1", 0);
sym.setVariable("fouten1", 0)
//var game = sym.getComposition().getStage();
var cirkels = [];
var test1 = "bommen";
var score2 = sym.getComposition().getStage().getVariable("score1");
var fouten = sym.getComposition().getStage().getVariable("fouten1");
var cirkelStart = {x:180,y:190};
var cirkelSpacing = {x:170,y:170};
function init(){
initPlayer();
spawnCirkels();
}
//this is for score and error updating
function initPlayer(){
sym.$("scoreTxt").html(score2);
sym.$("hpTxt").html(fouten);
}
// create symbols on a grid
function spawnCirkels(){
var cirkel;
var el;
var i;
var xPos = cirkelStart.x;
var yPos = cirkelStart.y;
var col = 0;
for(i = 0;i < 15;i++){
cirkel = sym.createChildSymbol("Cirkel", "Stage");
cirkel.play(Math.random() * 1000);
cirkels.push(cirkel);
el = cirkel.getSymbolElement();
el.css({"position":"absolute", "top":yPos + "px", "left":xPos + "px"});
xPos += cirkelSpacing.x;
col++;
if(col === 5){
col = 0;
yPos += cirkelSpacing.y;
xPos = cirkelStart.x;
}
}
}
init();
If anyone sees what I am doing wrong let me know!
Thanks for your help anyway!

Convert all SVG text nodes into path nodes with Raphael JS

I'm attempting to write a RaphaelJS function that will take existing text nodes within a Raphael paper instance and convert them into paths.
The goal is to replicate the position, size and attribute of the text exactly as it appears on the page, but have it rendered using paths instead of text. I cannot initially render the text using the Raphael paper.print() function because the text is updated dynamically and requires "text" based attributes to do so. Converting existing text nodes to paths will occur as the "final" step in the process (after the text modifications are complete).
I am doing this to eliminate the need for having fonts installed to view or handle the SVG later.
The challenges I face are:
Text nodes may include tspans with x and dy definitions. The paths created must line it perfectly witch each of the childNode letters (tspans).
Retrieving the actual position data of text node, and each tspan. This is where I'm having trouble and hopefully someone with more experience can assist me. Since stroke widths and other attributes affect the positioning/bbox values, I'm not sure what's the most efficient method of obtaining the correct positioning data for the text.
What I have tried so far:
A simple breakdown of my code.
I wrote a custom attribute function, textFormat, that formats the text in a staggered formation. This function parses the text node, splits it by each letter adding a new line \n character, and adjusts the positioning to look staggered.
The textToPaths function is a paper function that is supposed to loop through the paper nodes, and convert all found text nodes into path using the Raphael paper.print() function. This is the function I am having trouble with.
View the Complete JSFiddle Example Here
The problem code
I'm not sure how to obtain accurate and consistent x and y values to pass into the paper.print() function. Right now, I am using getBoundingClientRect() but it's still off and skewed. My assumption is the stroke widths are affecting the x and y calculations.
//Loop through each tspan and print the path for each.
var i,
children = node.node.childNodes,
len = children.length;
for (i = 0; i < len; i++) {
var tspan = children[i],
tspanText = tspan.innerHTML,
x = tspan.getBoundingClientRect().left - node.node.getBoundingClientRect().left, //How do I get the correct x value?
y = tspan.getBoundingClientRect().top - node.node.getBoundingClientRect().top; //How do I get the correcy y value?
var path = paper.print(x, y, tspanText, font, fontSize),
attrs = node.attrs;
delete attrs.x;
delete attrs.y;
path.attr(attrs);
path.attr('fill', '#ff0000'); //Red, for testing purposes.
}
Complete Code View the JSFiddle Example
//Register Cufon Font
var paper = Raphael(document.getElementById('paper'), '600', '600');
var text1 = paper.text(100, 100, 'abc').attr({fill: 'none',stroke: '#000000',"stroke-width": '12',"stroke-miterlimit": '1',"font-family" : "Lobster", "font-size": '30px','stroke-opacity': '1'});
var text2 = paper.text(100, 100, 'abc').attr({fill: 'none',stroke: '#ffffff',"stroke-width": '8',"stroke-miterlimit": '1',"font-family" : "Lobster", "font-size": '30px','stroke-opacity': '1'});
var text3 = paper.text(100, 100, 'abc').attr({fill: '#000000',stroke: '#ffffff',"stroke-width": '0',"stroke-miterlimit": '1',"font-family" : "Lobster", "font-size": '30px','stroke-opacity': '1'});
var text = paper.set(text1, text2, text3);
text.attr('textFormat', 'stagger');
/* paper.textToPaths
* Description: Converts all text nodes to paths within a paper.
*
* Example: paper.textToPaths();
*/
(function(R) {
R.fn.textToPaths = function() {
var paper = this;
//Loop all nodes in the paper.
for (var node = paper.bottom; node != null; node = node.next ) {
if ( node.node.style.display === 'none' || node.type !== "text" || node.attrs.opacity == "0") continue; //skip non-text and hidden nodes.
//Get the font config for this text node.
var text = node.attr('text'),
fontFamily = node.attr('font-family'),
fontSize = parseInt(node.attr('font-size')),
fontWeight = node.attr('font-weight'),
font = paper.getFont(fontFamily, fontWeight);
//Loop through each tspan and print the path for each.
var i,
children = node.node.childNodes,
len = children.length;
for (i = 0; i < len; i++) {
var tspan = children[i],
tspanText = tspan.innerHTML,
x = tspan.getBoundingClientRect().left - node.node.getBoundingClientRect().left, //How do I get the correct x value?
y = tspan.getBoundingClientRect().top - node.node.getBoundingClientRect().top; //How do I get the correcy y value?
var path = paper.print(x, y, tspanText, font, fontSize),
attrs = node.attrs;
delete attrs.x;
delete attrs.y;
path.attr(attrs);
path.attr('fill', '#ff0000'); //Red, for testing purposes.
}
}
};
})(window.Raphael);
textToPaths = function() {
//Run textToPaths
paper.textToPaths();
};
/* Custom Element Attribute: textFormat
* Description: Formats a text element to either staggered or normal text.
*
* Example: element.attr('textFormat, 'stagger');
*/
paper.customAttributes.textFormat = function( value ) {
// Sets the SVG dy attribute, which Raphael doesn't control
var selector = Raphael.svg ? 'tspan' : 'v:textpath',
has = "hasOwnProperty",
$node = $(this.node),
text = $node.text(),
$tspans = $node.find(selector);
console.log('format');
switch(value)
{
case 'stagger' :
var stagger = function(el) {
var R = Raphael,
letters = '',
newline = '\n';
for (var c=0; c < text.length; c++) {
var letter = text[c],
append = '';
if(c < text.length - 1)
append = newline;
letters += letter+append;
}
el.attr('text', letters);
var children = el.node.childNodes;
var i,
a = el.attrs,
node = el.node,
len = children.length,
letterOffset = 0,
tspan,
tspanHeight,
tspanWidth,
tspanX,
prevTspan,
prevTspanRight = 0,
tspanDiff = 0,
tspanTemp,
fontSize,
leading = 1.2,
tempText;
for (i = 0; i < len; i++) {
tspan = children[i];
tspanHeight = tspan.getComputedTextLength();
tspanWidth = tspan.getComputedTextLength();
tspanX = tspan.getAttribute('x'),
prevTspanRight = tspan.getBoundingClientRect().right
if(tspanX !== null)
{
tspanDiff = tspanDiff + prevTspanRight - tspan.getBoundingClientRect().left;
var setX = parseInt(tspanX) + parseInt(tspanDiff);
tspan.setAttribute('x', setX);
tspan.setAttribute('dy', 15);
}
prevTspan = tspan;
}
}
stagger(this);
break;
case 'normal' :
this.attr('text', text);
break;
default :
this.attr('text', text);
break;
}
eve("raphael.attr.textFormat." + this.id, this, value);
// change no default Raphael attributes
return {};
};
staggerText = function() {
//Run textToPaths
text.attr('textFormat', 'stagger');
};
If anyone can help me solve this problem I would greatly appreciate it. Thanks!
You can convert fonts to SVG/Canvas path commands using Opentype.js.
The lib will return to you a series of path drawing commands; these are intended for drawing on an HTML5 <canvas> element.
However it is trivial to build an SVG path with those commands since the font-conversion does not include any commands that are compatible with Canvas path drawing that would be incompatible with an SVG path command.

Categories

Resources