Canvas WrapText function with fillText - javascript

I am creating a multiple choice quiz, however, the questions are too many characters and won't display on 1 line. I understand I have to use the wrapText function in order to paragraph it, I am just unsure how to implement this within my code. The Questions are set as strings in a variable (Questions), with the answers being strings in variable (Options). textpos1, defines the coordinates where I want my question to start, with textpos2 - textpos4 defining the coordinates where the separate answers start. These coordinates are in these positions to align to my background image.
JSFiddle here with full code however it doesn't seem to like my BG image on there... http://jsfiddle.net/danielparry8/6U9Rn/
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var quizbg = new Image();
var Question = new String;
var Option1 = new String;
var Option2 = new String;
var Option3 = new String;
var mx=0;
var my=0;
var CorrectAnswer = 0;
var qnumber = 0;
var rightanswers=0;
var wronganswers=0;
var QuizFinished = false;
var lock = false;
var textpos1=25;
var textpos2=145;
var textpos3=230;
var textpos4=325;
var Questions = ["Which Manchester United Player won \n the 2008 Golden Boot with 31 Goals?","At which club did Bobby Charlton start his football career?","Which year did Wayne Rooney win the BBC Young Sports Personality of the year award?"];
var Options = [["Cristiano Ronaldo","Wayne Rooney","Ryan Giggs"],["Manchester United","Manchester City","Chelsea"],["2002","2003","2004"]];
The 'setQuestions' function grabs the appropriate question and answers and uses fillText to apply them to the canvas, my only issue being that the Question is displayed on one continuos line.
SetQuestions = function(){
Question=Questions[qnumber];
CorrectAnswer=1+Math.floor(Math.random()*3);
if(CorrectAnswer==1){Option1=Options[qnumber][0];Option2=Options[qnumber][1];Option3=Options[qnumber][2];}
if(CorrectAnswer==2){Option1=Options[qnumber][2];Option2=Options[qnumber][0];Option3=Options[qnumber][1];}
if(CorrectAnswer==3){Option1=Options[qnumber][1];Option2=Options[qnumber][2];Option3=Options[qnumber][0];}
context.textBaseline = "middle";
context.font = "16pt sans-serif,Arial";
context.fillText(Question,20,textpos1);
context.font = "14pt sans-serif,Arial";
context.fillText(Option1,20,textpos2);
context.fillText(Option2,20,textpos3);
context.fillText(Option3,20,textpos4);
}
Below is a wrapText function i have tried to implement into my design with no avail, If anyone can help that is greatly appreciated. Thanks!
function wrapText(context, text, x, y, maxWidth, fontSize, fontFace){
var words = text.split(' ');
var Questions = '';
var lineHeight=fontSize;
context.font=fontSize+" "+fontFace;
for(var n = 0; n < words.length; n++) {
var testLine = Questions + words[n] + ' ';
var metrics = context.measureText(testLine);
var testWidth = metrics.width;
if(testWidth > maxWidth) {
context.fillText(Questions, x, y);
Questions = words[n] + ' ';
y += lineHeight;
}
else {
Questions = testLine;
}
}
context.fillText(Questions, x, y);
return(y);
}

I experimented with your example code. You don't indicate exactly what you expect to be passed in the 'fontSize' argument to your 'wrapText' function, but I assumed it would be something like '16pt'. So you have this code:
var lineHeight=fontSize;
context.font=fontSize+" "+fontFace;
so now...
lineHeight === '16pt'
context.font === '16 pt sans-serif,Arial'
Then later your code says:
y+= lineHeight;
Now you have a problem, because adding '16pt' (lineHeight) to y doesn't make any sense. The value for 'y' is is a simple integer value (representing pixels). Trying to add the string, '16pt' to it results in a strange value and strange results.
I used the code from JSFiddle, copied it to my own sandbox. Then I cut/pasted from above, the code for wrapText.
I substituted for:
context.fillText(Question, 20, textpos1);
with:
wrapText(context,Question,20,textpos1,500,'16pt','sans-serif,Arial');
inside wrapText where
y = lineHeight;
instead use:
y = 16;
You should see your wrapped line. y = 16 is just an arbitrary number I chose. It is representing pixels...it isn't the same thing as 16pt.

Here's an example and a Demo: http://jsfiddle.net/m1erickson/QGz2R/
If you create a "class" then you can reuse that "class" to store and draw as many questions as you need.
First, create a Question & Answer class that holds:
the question text
each answer text
the bounding box of each answer that's drawn on the canvas
the correct answer
Example Code:
// a Question & Answer "class"
function QA(question,options,correctOption){
this.QBB=null; // the bounding box for the wrapped question text
this.Q=question;
this.A=[];
this.correct=correctOption;
for(var i=0;i<options.length;i++){
this.A.push({text:options[i],BB:null});
}
}
// Draw the question and possible answers on the canvas
// At the same time calculate the bounding boxes(BB)
// of each answer so that the BB can be used to test
// if the user clicked in the correct answer
QA.prototype.draw=function(x,y,maxWidth){
var BB=this.QBB=wrapText(this.Q,x,y,maxWidth,14,"verdana");
for(var i=0;i<this.A.length;i++){
y+=BB.height;
BB=this.A[i].BB=wrapText(i+". "+this.A[i].text,x,y,maxWidth,14,"verdana");
ctx.strokeRect(BB.x,BB.y,BB.width,BB.height);
}
return({x:x,y:y});
}
// test if the supplied mouseX/mouseY is inside any answer
QA.prototype.hitAnswer=function(x,y){
for(var i=0;i<this.A.length;i++){
var bb=this.A[i].BB;
if(x>bb.x && x<bb.x+bb.width && y>bb.y && y<bb.y+bb.height){
return(i);
}
}
return(-1);
}
Next,
Listen for mousedown events
Test if the user has clicked in any answer. You do this by testing if the mouse was clicked inside the bounding box of any of the answers.
If the user clicked in an answer alert whether they selected the correct answer or not.
Example code:
// Listen for mousedown and check if the user has
// clicked in an answer to the question
function handleMouseDown(e){
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
var answer=q.hitAnswer(mouseX,mouseY);
if(answer==q.correct){
$results.text("Correct! #"+q.correct+" is the answer");
}else{
$results.text("Sorry! #"+answer+" is not the answer");
}
}
And here is the slightly modified wrapText code that now returns the bounding box of the text it just wrapped on the canvas:
// wrap the specified text and draw it to the canvas
// and return the bounding box of that wrapped text
function wrapText(text, x, y, maxWidth, fontSize, fontFace){
var startY=y;
var words = text.split(' ');
var line = '';
var lineHeight=fontSize+2;
ctx.font=fontSize+" "+fontFace;
y+=fontSize;
for(var n = 0; n < words.length; n++) {
var testLine = line + words[n] + ' ';
var metrics = ctx.measureText(testLine);
var testWidth = metrics.width;
if(testWidth > maxWidth) {
ctx.fillText(line, x, y);
line = words[n] + ' ';
y += lineHeight;
}
else {
line = testLine;
}
}
ctx.fillText(line, x, y);
return({x:x,y:startY,width:maxWidth,height:y-startY+4});
}

Related

how to get nearest anchor from the current mouse position on mouse move

$('p').on('mousemove',function(event) {
link = $(event.target).find('a');
//to find the nearest link and store in the link variable
console.log(link.html());//to print the link in console
});
I tried this but im only able to find the first link within a paragraph
but i want to find the links near to my mouse position
You can use document.elementFromPoint(x, y);
and create some kind of jQuery plugin
First, to see the code in action check the fiddle
Example usage of the code below:
$("p").nearest("a", 10);
But the code below just checks the box around the element with the provided distance. If it doesn't return any elements, you can use it further and check the box around the elements by distance of 20px, then 30px and so on. Depends on your needs.
$.fn.nearest = function(selector, radius) {
var position = this.offset();
if (!radius) radius = 10; // pixels
var positions = [];
var elements = [];
// so, move up and left by the value of radius variable (lets say its 10)
// start from the -10 -10 px corner of the element and move all the way to
// +10 +10 coordinats to the right bottom corner of the element
var startX = position.left - radius;
var startY = position.top - radius;
var endX = position.left + this.outerWidth(true) + radius;
var endY = position.top + this.outerHeight(true) + radius;
var positions = [];
// create horizontal lines
// --------------
// your element
// --------------
for (var x = startX; x < endX; x++) {
// creates upper line on the startY coordinate
positions.push([x, startY]);
// creates bottom line on the endY coordinate
positions.push([x, endY]);
}
// create the vertical positions
// | |
// | your element |
// | |
for (var y = startY; y < endY; y++) {
// creates the left line on the startX coordinate
positions.push([startX, y]);
// creates the right line on the endX coordinate
positions.push([endX, y]);
}
// so now the positions array contains all the positions around your element with a radius that you provided
for (var i = 0; i < positions.length; i++) {
// just loop over the positions, and get every element
var position = positions[i];
var x = position[0];
var y = position[1];
var element = document.elementFromPoint(x, y);
// if element matches with selector, save it for the returned array
if ($(element).is(selector) && elements.indexOf(element) === -1) elements.push(element);
}
return $(elements);
}
you can also try hover instead of mouse move
<p class="demo1">
some examples some examples anchor1 some examples some examples anchor1some examples some examples some examples some examples anchor1some examples some examples
</p>
<div id="demo2">hi</div>
$('.demo2 a').hover(function (event) {
link = $(event.target);
//to find the nearest link and store in the link variable
$("#demo2").text(link);
});

how to detect and move/drag the free flow drawn lines in html canvas?

My implementation is this:
Detect if mousedown and mousemove and if true then draw and saved the points in an array.
In my mousemove I will convert the points that will be drawn in
I converted the curPath to (Date,value) then to (X and Y-axis) so that they will be saved in the implementation in my canvas.
My problem is that how will I detect points[] ? so that I can highlight it and drag as well.
UPDATE.
This is bigger than I expected. I will continue to improve the quality of the answer as I go. See the bottom of the answer for status.
Picking.
The simplest way is to check how far the mouse is from each point in the line, and highlight the line that has the closest point. The problem is that when you have many lines and lots of points it slows down and become unusable.
Another ways is to store some extra info on each line to help you vet lines that are not going to be picked. In the example I create a bounding box for each line and check if the mouse is inside or near that box. If so then I search the line some more checking each line segment and keeping the line that is closest to the mouse.
Some of the function to look at.
Helpers.prototype.resetExtent();
Helpers.prototype.extent();
Helpers.prototype.copyOfExtent();
Used to find the bounding box. (extent)
function refreshLine(line);
Called after a line is drawn, it takes a set of points drawn and adds the bounding box (extent), plus other stuff for the demo.
function findLineAt(x,y){
This function takes the x,y position of the mouse (or what ever) and returns the closest line within 20 pixels. It first checks the bounding box, if that passes it calls
Helpers.prototype.getDistToPath = function (line,x,y) {
This gets the line as just a set of points and checks the distance to the center of each line. It also checks if the check needs more details and calls the function.
Helpers.prototype.distPointToLine = function (x, y, x1, y1, x2, y2) {
This function will return the shortest distance from a point to a line. x,y is the point x1,y1,x2,y2 is the line. It does not check the line segment but the line which is infinitely long. Helpers.lineSegPos property will hold the normalised position of the closest point on the line. If you need it.
So back to findLineAt(x,y), after all those calls it will return the line if found or undefined if not.
Highlight and dragging.
Highlighting is up to you. I just cycle the hue of the line closest very quickly. You may wish to put a bounding box around it. Easy to do as all you do is redraw the closest line each update.
How its works
The main loop.
update()
Handles the main loop, is called 60 times a second and has to parts, the Draw section is for drawing and pick for picking. See if(drawMode==="Pick"). The mouse is only read in the update, the mouse is set independently by the mouse listener. At the end of every loop I save the mouse button state mouse.lastButton so that I can check when the mouse moves down and up.
In the pick part if the mouse is not down I call the findLineAt function. If I find a line (line !== undefined) I highlight the line by changing its colour and drawing it.
Because every update I have the current mouseButton state and what it was last update, I will know when the mouse button first moves down because mouse.button is true and mouse.lastButton is false. If there is a line near the mouse, I record the mouse position in dragOffX and dragOffY and set a flag dragging to true. I also draw the canvas onto another canvas to keep as background. At this point I also check which mouse button is down. If right button I copy the line and set it as the line to be dragged, or if the middle button I search all the lines to find its index in the lineArray and delete it, then just redraw.
Next update (1/60th second later) and dragging flag is true mouse.button is true and lastLine (the line that was closest) is not undefined I know I am dragging a line. I clear the canvas, draw the saved copy of the canvas (it's faster to draw that then redraw all the lines again especially if you have 100's of lines with 1000's of points), and then redraw the line I am dragging.
To workout where to draw the dragged line I get the distance the mouse is from dragOffX and dragOffY and set the transpose part of setTransform(1, 0 , 0, 1, mouse.x - dragOffX, mouse.y - dragOffY) to that distance. That has the effect of moving the line by the drag amount. I keep doing this until the mouse button is up.
Drop
Next update and mouse.button is up.
If the mouse button is up and the dragging flag is true then I must drop the line. At this point a get the mouse distance from dragOffX dragOffY and add it to each point in the line. Effectively moving the line. I also update the bounding box. I then clear the screen and redraw all the line, that removes the old position of the line from the canvas and put it at it's new home.
Done.
The code grew a little long. If this answer gets some support then I will clean it up some more. If not well then it does not matter..
It covers the basics of your question, detecting and moving points via mouse action. Highlighting and moving lines made of sets of points. My point are arrays of objects each with an x and y. Each line is stored in the lineArray a line has style, extent, id properties, and an array called line with all the points.
There is one mouse handler that takes the required mouse events. Move, mouse down and up, and mouse out. Mouse out stops the mouse locking up by turning the mouse buttons off. I also stop the context menu while the mouse is over the canvas.
I use requestAnimationFrame to call update to keep it all running smoothly.
I hope this helps you. I will improve it if it is what you are after. If not you will have to give a litte more info. Please do ask if you have problems.
Updates.
Added bounding box and improved the Helpers.prototype.getDistToPath(line,x,y) which I forgot to fix last night. Now its quicker and does not miss lines parallel to x and y axis. Moved screen redraw to accommodate the bounding box and add more comments.
Please do not hesitate to ask if you have question to this problem.
function log(){}; // duck up the logs
customCursors ={
encoding:"url('data:image/png;base64,",
drag_small : {
center : " 25 25,",
image : "iVBORw0KGgoAAAANSUhEUgAAADMAAAAzCAYAAAA6oTAqAAACQElEQVRoQ+2azW7DIAyAYZdJW6vlVmmnvcLe/yH2CjtN6i1Tu0m9rIMsJIYChmCvCWkuqZSA/fkPQyoF83VWl5RSqJtQd8kpjnVyB4QdiA0GghhvcHuIBcYH8h9A5DAxEG4gUhgN8rzbiY/9Hs1zjpAjg0nxiEtIDUQCMwWEI+SKYfJBzorDFkvloSvAXKZTs92K9nAoXlTJYFwV9YofunyNAEWHQALjU9qETijpA2OK9CkaHLJ8NYumBrzBoMss/sK6wkyHDLRJyp6EKsxyZUc9Y5R62mzE5/GYvB+hhNFVMVV+EMZVKGeVpoYxwYHp4IUp3VhxwehwjwFdwIQUwawC84oTJgZkwaQogRfIvzcA/DCkb1m63Eu9sE4CFqQBxgty+hLi/mHocnMOVyzFf96EuHv1AkKopmlE27YW5wiuDHD6Vvo8Ds/daOlggh7pYMbBqdaEnon9zpmve9ejDwSS0f3IRBgYGqOwF2W0dysEKWCskO4dkz1vbADMF9PaQ6OF8qBECT1ndZ6pJ2eMa6upZlGg/mFunF91ncGAFtcBxIDmApPVm4WA5gCD6bCO/Qz0EFzMFrvTnLoip3TfKUbJlb+uA41c60S7cPUQS+Ip8syYm2eg9dzjoMFK/edy19KxTqI0j4o9Y5LdVXqxXwFy+zYXfHbfZ9IPKWb85QyrXlh1oqxuxTmDdduJ22sSPUgmgUBV/A8gx0OUoWX1jVhMT3leVW8WKgpcHmFtZ3whxw2iZZIWAF9IOod/rPJ+AQ3iOFgpekFcAAAAAElFTkSuQmCC')"
},
}
function setCursor (name){
if(name === undefined){
canvas.style.cursor = "default";
}
if(customCursors[name] !== undefined){
var cur = customCursors[name];
canvas.style.cursor = customCursors.encoding + cur.image + cur.center + " pointer";
}else{
canvas.style.cursor = name;
}
}
// get canvas button and creat context2D
var canvas = document.getElementById("canV");
var ctx = canvas.getContext("2d");
var but = document.getElementById("redrawAllID");
but.addEventListener("click",function(){
if(drawMode === "Pick"){
drawMode = "Draw";
but.value = "Draw Mode";
}else{
drawMode = "Pick";
but.value = "Pick Mode";
lastLine = undefined;
backGroundImage.ctx.clearRect(0,0,backGroundImage.width,backGroundImage.height);
backGroundImage.ctx.drawImage(canvas,0,0);
}
})
// Groover Bitmaps API dependency replacement
// Extracted from Groover.Bitmaps
var createImage= function(w,h){ // create a image of requier size
var image = document.createElement("canvas");
image.width = w;
image.height =h;
image.ctx = image.getContext("2d"); // tack the context onto the image
return image;
}
var backGroundImage = createImage(canvas.width,canvas.height);
if(!mouse){
// get all the mouse events
canvas.addEventListener('mousemove',mouseMoveEvent);
canvas.addEventListener('mousedown',mouseMoveEvent);
canvas.addEventListener('mouseup' ,mouseMoveEvent);
canvas.addEventListener('mouseout' ,mouseMoveEvent);
canvas.addEventListener("contextmenu", function(e){ e.preventDefault();}, false);
// helper for random colour
var mouse = { // mouse data
x:0,
y:0,
button:false,
lastButton:false, // need this to see when the mouse goes down
which:[false,false,false],
};
}
function mouseMoveEvent(event){// handle all canvas mouse events as they come in
// get new mouse positions
mouse.x = event.offsetX;
mouse.y = event.offsetY;
if(mouse.x === undefined){ // if firefox
mouse.x = event.clientX;
mouse.y = event.clientY;
}
if(event.type === "mouseout"){
mouse.button = false;
mouse.which[0] = false;
mouse.which[1] = false;
mouse.which[2] = false;
}
if(event.type === "mousedown"){ // now see if there is extra info
mouse.button = true;
mouse.which[event.which-1] = true;
}
if(event.type === "mouseup"){ // now see if there is extra info
mouse.button = false;
mouse.which[event.which-1] = false;
}
event.preventDefault();
}
// because forEach is too slow
if (Array.prototype.each === undefined) {
Object.defineProperty(Array.prototype, 'each', {
writable : false,
enumerable : false,
configurable : false,
value : function (func) {
var i,
returned;
var len = this.length;
for (i = 0; i < len; i++) {
returned = func(this[i], i);
if (returned !== undefined) {
this[i] = returned;
}
}
}
});
}
// helper functions
function Helpers(){
}
Helpers.prototype.randomColour = function(){
return "hsl("+Math.floor(Math.random()*360)+",100%,50%)";
}
Helpers.prototype.occilatingColour = function(){
var t = (new Date()).valueOf()
return "hsl("+(Math.floor(t/2)%360)+",100%,50%)";
}
// used for building up the extent of a cloud of points
Helpers.prototype.resetExtent = function(){
if(this.extentObj === undefined){ // check if the extentObj is there
this.extentObj = {}; // if not create it
}
this.extentObj.minX = Infinity;
this.extentObj.minY = Infinity;
this.extentObj.maxX = -Infinity;
this.extentObj.maxY = -Infinity;
}
Helpers.prototype.extent = function( p) { // add a point to the extent
this.extentObj.minX = Math.min(this.extentObj.minX, p.x);
this.extentObj.minY = Math.min(this.extentObj.minY, p.y);
this.extentObj.maxX = Math.max(this.extentObj.maxX, p.x);
this.extentObj.maxY = Math.max(this.extentObj.maxY, p.y);
}
Helpers.prototype.copyOfExtent = function () { // get a copy of the extent object
return {
minX : this.extentObj.minX,
minY : this.extentObj.minY,
maxX : this.extentObj.maxX,
maxY : this.extentObj.maxY,
centerX : (this.extentObj.maxX-this.extentObj.minX)/2,
centerY : (this.extentObj.maxY-this.extentObj.minY)/2,
width:this.extentObj.maxX-this.extentObj.minX,
height:this.extentObj.maxY-this.extentObj.minY,
};
}
Helpers.prototype.getID = function(){ // return a unique ID for this session
if(this.id === undefined){
this.id = 0;
}
this.id += 1;
return this.id;
}
// function to get distance of point to a line
Helpers.prototype.distPointToLine = function (x, y, x1, y1, x2, y2) {
var px = x2 - x1;
var py = y2 - y1;
var u = this.lineSegPos = Math.max(0, Math.min(1, ((x - x1) * px + (y - y1) * py) / (this.distSqr1 = (px * px + py * py))));
return Math.sqrt(Math.pow((x1 + u * px) - x, 2) + Math.pow((y1 + u * py) - y, 2));
}
// function to get the distance of a point to a set of point describing a line
Helpers.prototype.getDistToPath = function (line,x,y) {
var i,len, lineLen,dist;
len = line.length;
x1 = line[0].x;
y1 = line[0].y;
var minDist = Infinity;
for(i = 1; i < len-1; i++){
var near = false;
x2 = line[i].x;
y2 = line[i].y;
lineLen = Math.hypot(x1-x2,y1-y2);
dist = Math.hypot((x1+x2)/2-x,(y1+y2)/2-y);
minDist = Math.min(minDist,dist);
if(dist < lineLen ){
minDist = Math.min(minDist,helpers.distPointToLine(x,y,x1,y1,x2,y2));
}
if(minDist < minDistToPass){
return minDist;
}
x1 = x2;
y1 = y2;
}
return minDist;
}
var helpers = new Helpers();
// Stuff for paths and drawing
var lineArray = []; // list of paths
var lastLine; // last line drawn
var points; // current recording path
var drawing = false; // flag is mouse down and drawing
var dragging = false;
var dragOffX;
var dragOffY;
var drawMode = "Draw";
var minDistToPass = 2; // If a line is closer than this then stop search we found the winning line
// functions to redraw all recorded lines
function redrawAll(){ // style to draw in
ctx.clearRect(0,0,canvas.width,canvas.height);
lineArray.each(function(p){ // draw each one point at atime
redraw(p,p.style);
})
}
// lineDesc is a line and its description
// style is a the style to draw the line in.
// withBox if true draw bounding box [optional]
function redraw(lineDesc,style,withBox){ // draws a single line with style
var line = lineDesc.line;
var len = line.length;
var i;
ctx.beginPath(); //
ctx.strokeStyle = style.colour; // set style and colour
ctx.lineWidth = lineDesc.style.width;
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.moveTo(line[0].x,line[0].y); // move to the first pont
for(i = 1; i < line.length; i++){ // lineto all the rest
ctx.lineTo(line[i].x,line[i].y);
};
ctx.stroke(); // stroke
if(withBox){
var w = Math.ceil(lineDesc.style.width/2); // need the lines width to expand the bounding box
ctx.lineWidth = 1;
ctx.strokeStyle = "black";
ctx.strokeRect( // draw the box around the line
lineDesc.extent.minX-w,
lineDesc.extent.minY-w,
lineDesc.extent.width+w*2,
lineDesc.extent.height+w*2
)
}
// done
}
// Finds the closest line and returns it. If no line can be found it returns undefined.
function findLineAt(x,y){
var minDist = 20; // Set the cutoff limit. Lines further than this are ignored
var minLine;
var w;
lineArray.each(function(line){ // do ech line one at a time
w = line.style.width;
if(x >= line.extent.minX-w && x <= line.extent.maxX+w && // is the point inside the bounding
y >= line.extent.minY-w && y <= line.extent.maxY+w){ // boc
var dist = helpers.getDistToPath(line.line,x,y); // if so then do a detailed distance check
if(dist < minDist){ // is the distance to the line less than any other distance found
minDist = dist; // if so remember the line
minLine = line;
}
}
dist = Math.hypot(line.extent.centerX-x,line.extent.centerY-y); // check the distance to the
if(dist<minDist){ // center of the bounding boc
minDist = dist; // use this one if bound box center if close
minLine = line;
}
});
return minLine;
}
function refreshLine(line){ // updates the line to get extend and add stuff if needed
// a good place to smooth the line if need
if(!line.isLine){
var newLine = {}; // new object
newLine.isLine = true; // flag to indicate that the line has been updated
newLine.line = line; // attach the line
newLine.id = helpers.getID(); // get a unique Id for the line
newLine.style = { // give it a style
colour:helpers.randomColour(),
width:Math.random()*4+10,
};
}else{
var newLine = line;
}
var extent = helpers.extent.bind(helpers)
helpers.resetExtent();
line.each(extent);
newLine.extent = helpers.copyOfExtent();
return newLine;
}
function update(){ // one animframe
if(drawMode === "Draw"){
if(!mouse.lastButton && mouse.button ){ // if the mouse but just down;
points = []; // create an new array
drawing = true; // flag drawinf
lineArray.push(points); // save the point array onto the pointsArray
points.push({x:mouse.x,y:mouse.y}); // add the first point
setCursor("none");
}else
if(drawing && mouse.button){ // while the mouse is down keep drawing
points.push({x:mouse.x,y:mouse.y}); // save new point
var p1 = points[points.length-2]; // get last line seg and draw it
var p2 = points[points.length-1];
ctx.lineWidth = 1;
ctx.strokeStyle = "black";
ctx.beginPath();
ctx.moveTo(p1.x,p1.y);
ctx.lineTo(p2.x,p2.y);
ctx.stroke();
}else{
if(drawing){ // if drawing and mouse up
points.push({x:mouse.x,y:mouse.y}); // add the last point
lineArray.push(points = refreshLine(lineArray.pop()))
// redraw the newly draw line
redraw(points,points.style);
drawing = false; // flag that drawing is off.
}else{
setCursor("crosshair");
}
}
}else
if(drawMode = "Pick"){
if(!dragging && !mouse.button){ // is the mouse near a line and not dragging
ctx.clearRect(0,0,canvas.width,canvas.height); // clear background
ctx.drawImage(backGroundImage,0,0); // draw copy of existing lines
var line = findLineAt(mouse.x,mouse.y); // find the line
if(line !== undefined){ // is a line is near
setCursor("drag_small"); // highlight it
lastLine = line; // remember it
// draw it hightlighted with bounding box.
redraw(lastLine,{colour:helpers.occilatingColour(),width:lastLine.width},true);
}else{
setCursor(); // not near a line so turn of cursoe
}
}else // next check if the mouse has jsut been click to start a drag.
if(lastLine !== undefined && !mouse.lastButton && mouse.button){
if(mouse.which[2]){ // Check which button. Right? then copy
var newLine = [];
lastLine.line.each(function(p){newLine.push({x:p.x,y:p.y})});
newLine = refreshLine(newLine)
newLine.style = lastLine.style;
lastLine = newLine;
lineArray.push(newLine)
}else
if(mouse.which[1]){ // Check which button. Middle? then delete
var index;
lineArray.each(function(line,i){
if(line.id === lastLine.id){
index = i;
}
})
if(index !== undefined){
lineArray.splice(index,1);
}
ctx.clearRect(0,0,canvas.width,canvas.height);
redrawAll();
lastLine = undefined;
if(lineArray.length === 0){
drawMode = "Draw";
but.value = "Draw Mode";
}
}
if(lastLine !== undefined){
dragging = true;
dragOffX = mouse.x;
dragOffY = mouse.y;
// backGroundImage.ctx.clearRect(0,0,canvas.width,canvas.height);
// backGroundImage.ctx.drawImage(canvas,0,0);
}
}else{
if(dragging && !mouse.button){ // Drop is dragging true and not mouse down
dragging = false;
var ox = mouse.x-dragOffX; // Find the drag offset
var oy = mouse.y-dragOffY;
helpers.resetExtent(); // get ready for new bounding box.
lastLine.line.each(function(p){ // move each point of the line
p.x += ox;
p.y += oy;
helpers.extent(p); // and test the bounding box
return p;
})
lastLine.extent = helpers.copyOfExtent(); // get the new boundong box
ctx.clearRect(0,0,canvas.width,canvas.height);
redrawAll();
backGroundImage.ctx.clearRect(0,0,backGroundImage.width,backGroundImage.height);
backGroundImage.ctx.drawImage(canvas,0,0);
}else
if(dragging){ // if dragging
ctx.clearRect(0,0,canvas.width,canvas.height); // clear
ctx.drawImage(backGroundImage,0,0); // draw existing lines
var ox = mouse.x-dragOffX; // get the drag offset
var oy = mouse.y-dragOffY;
ctx.setTransform(1,0,0,1,ox,oy); // translate by drag offset
redraw(lastLine,lastLine.style); //draw the dragged line
ctx.setTransform(1,0,0,1,0,0); // reset transform
}
}
}
mouse.lastButton = mouse.button; // set the last button state
window.requestAnimationFrame(update); // request a new frame
}
window.requestAnimationFrame(update)
.canC {
width:256px;
height:256px;
border:black 2px solid;
}
.info{
font-size:x-small;
}
<input type="button" id="redrawAllID" value="Click to Pick"></input>
<div class="info">Mouse down to draw.In pick mode mouse hover over line.<br> Left Button drag,middle delete, right copy.</div>
<canvas class="canC" id="canV" width=256 height=256></canvas>

Formatting text on HTML canvas [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
I'm making a game using HTML Canvas and vanilla javascript. I'm new to javascript so this may be easier than I think. I have two array's of objects that represent cards, each object has a 'text' property of between 40-100 characters that's drawn onto the screen dynamically onto a card that is 130 x 70px.
I need to format the text to fit the width restriction of the card (130px) and create a new line whenever necessary.
Any help is appreciated
Edited to make clearer
You can use the measureText() method from the canvas API.
As noted by Ken Fyrstenberg in this awesome answer,
canvas' measureText doesn't currently support measuring height (ascent + descent).
Below attempt uses an hardcoded lineHeight value that you'd have to find before rendering text. Ken's answer does provide a way to programmatically find it.
[ Edit: thanks to markE's comment below, it now uses an approximation of 1.286*the font-size. ]
So here it is, dirty and there must be better ways to do so but anyway...
var input = document.querySelector('input');
input.addEventListener('keyup', write, false);
var c = document.createElement('canvas'),ctx = c.getContext('2d');
c.width = 400, c.height = 150; document.body.appendChild(c);
// simple box object for the card
var card = {x: 25, y: 25, w: 130, h: 70};
ctx.fillStyle = "#CCCCCC";
ctx.fillRect(card.x, card.y, card.w, card.h);
var fontSize = 12;
ctx.font=fontSize+'px arial';
// Margins multipliers are chosen arbitrarly here, just because they fit well to my eyes
var margins = {t: 1.25*fontSize, l: .7*fontSize, b: 2*fontSize, r: .7*fontSize},
marginsX = margins.l+margins.r,
marginsY = margins.t+margins.b;
// As suggested by markE, lineHeight is set to 1.286* the fontSize,
// for a calculated way, see Ken's answer here : https://stackoverflow.com/a/17631567/3702797
var lineHeight = 1.286*fontSize;
// just a shortcut
var lineWidth = function(text) {
return ctx.measureText(text).width;
};
function write() {
var txt = this.value;
// Redraw our card
ctx.fillStyle = "#CCCCCC";
ctx.fillRect(card.x, card.y, card.w, card.h);
// Split the input at any white space, you might want to change it
var txtLines = txt.split(/\s/);
var i = 0, maxWidth = card.w - marginsX;
if(lineWidth(txt[0])>card.w || lineHeight>card.h-(margins.t/4) ){
console.warn('TOO BIG FONT!!');
return;
}
while(txtLines[i]) {
// If our current line merged with the next line width isn't greater than the card one
if (txtLines[i+1] && lineWidth(txtLines[i] + ' ' + txtLines[i+1]) < maxWidth) {
// Merge them
txtLines[i] += ' ' + txtLines.splice(i + 1, 1);
}
else {
// Is the one word too big? --> Dirtyphenation !
if (lineWidth(txtLines[i]) > maxWidth) {
// Add a new index with the two last chars since we'll add a dash
txtLines.splice(i+1, 0, "");
// If it's still too big
while (lineWidth(txtLines[i]) > maxWidth) {
var lastChars = txtLines[i].length - 2;
// Append those two last chars to our new array index
txtLines[i+1] = txtLines[i].substring(lastChars) + txtLines[i+1];
// Remove them from our current index
txtLines[i] = txtLines[i].substring(0, lastChars);
}
// Add the final dash
txtLines[i] += "-";
}
// Is our text taller than the card height?
if (lineHeight*i > card.h-marginsY){
// If there is more text coming after...
if (txtLines[i+1]){
// ...and it fits in the line
if(lineWidth(txtLines[i]+' '+txtLines[i+1])<maxWidth){
continue;
}
// ...and it doesn't fit in the line
else{
// Does a single char fit with the ellipsis ?
if(lineWidth(txtLines[i][0]+'...')<maxWidth){
// remove a char until we can put our ellipsis
while (lineWidth(txtLines[i]+'...') > maxWidth){
txtLines[i] = txtLines[i].substring(0,txtLines[i].length-1)
}
}else{
return;
}
txtLines[i] += '...';
// remove the surplus from the array
txtLines = txtLines.slice(0,i+1);
}
}
// stop looping here since we don't have space anymore
break;
}
// Go to next line
i++;
}
}
ctx.fillStyle = "#000";
// Where to draw
var x = card.x + (margins.l);
var y = card.y + (margins.t);
// Iterate through our lines
for (var i = 0; i < txtLines.length; i++) {
ctx.fillText(txtLines[i], x, y + (i * lineHeight));
}
}
canvas {border: 1px solid}
<input type="text" />

How to add the vertical parallel lines in the rectangle?

I want to add the vertical lines when I draw rectangle. The no of lines is dependent on the user and can be read from the text box.
I know the logic but somehow I am not able to get the answer.
I am calculating the width of the rectangle and then diving the width on the basis of no of vertical lines.
Click the checkbox near rectangle and draw using mouse down events
Please let me know where I am going wrong.
function PlotPitch()
{
var iPatches = document.getElementById('txtPatchCount').value;
var iTop = mySel.y;
var iBottom = mySel.y + mySel.h;
var iLeft = mySel.x;
var iX = iLeft;
canvas = document.getElementById('canvas2');
context = canvas.getContext('2d');
for (var iPatch=1; iPatch<iPatches; ++iPatch) {
iX = iLeft + iPatch*mySel.w/iPatches;
context.moveTo(iX, iTop);
context.lineTo(iX, iBottom);
}
context.lineWidth=0.25;
context.stroke();
}
http://jsfiddle.net/K5wcs/4/
If I am adding this the code is breaking and I am not able to draw anything.
What you should do if you have 'strange' behaviour is to separate concerns, so in this case that could be by creating a function that you test separately, which draws the lines, then once it's tested ok, plug it in code by just calling the function. You should find quickly.
So begin by testing this :
function drawLines(Context, mySel, iPatches) {
var iTop = mySel.y;
var iBottom = mySel.y + mySel.h;
var iLeft = mySel.x;
var iX = iLeft;
var colWidth = mySel.w/iPatches ;
for (var iPatch=1; iPatch<iPatches; ++iPatch) {
iX += colWidth;
Context.moveTo(iX, iTop);
Context.lineTo(iX, iBottom);
}
Context.lineWidth=0.25;
Context.stroke();
}
Good luck.

Point in Polygon falsely detected

Derived from this: How to tackle diagonally stacked, rounded image background element hovers?
I made imagemap areas and transformed them for my case, but, now there is a problem with point in polygon hit detection.
It appears that only the bottom right quadrant is always correct, but, only if looking outside the ring - inside the detection might be still be incorrect. Other quadrants, outside the ring, occasionally report a positive hit where it should be false.
Fiddle: http://jsfiddle.net/psycketom/9J4dx/1/
The red lines are drawn from the polygon that's generated from data-map.
The blue line represents the polygon we're currently checking.
The point in polygon function comes from: https://github.com/substack/point-in-polygon
var pointInPolygon = function(point, vs)
{
// ray-casting algorithm based on
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
var x = point[0], y = point[1];
var inside = false;
for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
var xi = vs[i][0], yi = vs[i][1];
var xj = vs[j][0], yj = vs[j][1];
var intersect = ((yi > y) != (yj > y))
&& (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
if (intersect) inside = !inside;
}
return inside;
};
I cannot seem to understand what's the problem here.
Your mapToPolygon function doesn't convert the parsed points from string to number. Because of this, the pointInPolygon function ends up comparing the strings of the coordinates, not the actual coordinates. Using a parseInt on line 31 of the fiddle fixes the problem.
Create an off-screen canvas and use the context's .isPointInPath(x, y) function.
Loop through all of your polygons (in your example you would loop through them in reverse because you have smallest last. The smallest would be the highest level / greatest z-index).
On you get a hit (isPointInPath returns true) stop.
Something like...
var offcanvas = document.createElement("canvas");
...
var x = e.pageX - $ages.offset().left;
var y = e.pageY - $ages.offset().top;
revlayers.each(function() {
var $elm = $(this);
var poly = $elm.data("polygon");
var ctx = offcanvas.getContext("2d");
if(poly.length > 0) {
ctx.beginPath();
ctx.moveTo(poly[0][0], poly[0][1]);
for(var i=1; i<poly.length; i++) {
ctx.lineTo(poly[i][0], poly[i][1]);
}
if(ctx.isPointInPath(x, y)) {
hit.text($elm.attr("href"));
return false; // end the .each() loop
}
}
})

Categories