How to find text bounds for multilined text in P5.js? - p5.js

I need to create autosized text setting some box size:
text(textContent, textX, textY, textWidth, textHeight);
The problem is that the text is multilined because it can't place on one line. Ok, then I've tried to check textBounds - but it works only with one line of string. How to find bounds for text drawing in some box/rect (textWidth, textHeight)?

I am having a little difficulty understanding your question. But if you wanted to create a box that encompassed all the text you could implement something like the following:
Lets assume we have our text stored to objects like this to make the logic easier to read:
var text1 = {
"str" : "This Is Line One",
X1: 0,
Y1: 0,
X2: 300,
Y2: 100,
size:30
}
var text2 = {
"str" : "This Is Line 2",
X1: 0,
Y1: 30,
X2: 300,
Y2: 100,
size:20
}
X1,Y1 is the upper left coordinate, and X2,Y2 is the lower right.
Now we have a data model to work with. We simply have to combine the bounds into a single rectangle.
Remember:
Rectangles are Declared like this:
Rect(X,Y,Width,Height)
Which is exactly the information textBounds() gives us!
Here is a quick and dirty example of the math:
function draw() {
textSize(this.text1.size);
text(this.text1.str,this.text1.X1,this.text1.Y1,this.text1.X2,this.text1.Y2);
textSize(this.text2.size);
text(this.text2.str,this.text2.X1,this.text2.Y1,this.text2.X2,this.text2.Y2);
//Get Text Bounds object for each text
let text1box = this.font.textBounds(this.text1.str,this.text1.X1,this.text1.Y1,this.text1.size)
let text2box = this.font.textBounds(this.text2.str,this.text2.X1,this.text2.Y1,this.text2.size)
//Get the upper-left and lower-right coordinates of the bounding rectangle of all the text
let bounds = {
x1: min(text1box.X,text2box.X), //upper left x
y1: min(text1box.Y,text2box.Y), //uper left y
x2: max(text1box.x + text1box.w,text2box.x + text2box.w), //lower right x
y2: max(text1box.y + text1box.y,text2box.y + text2box.h) //lower right y
}
//if you want padding:
let padding = 2;
rect(
bounds.x1 - padding,
bounds.y1 - padding,
abs(bounds.x2, - bounds.x1) + padding, //Width of the bounding rectangle
abs(bounds.y2 - bounds.y1) + padding //Height of the bounding rectangle
)
}
To fully show the logic here is a quick diagram I made
The cool thing about this logic is that even if the text isn't lined up perfectly, it is still able to put a box around all the text.

Related

How to more + rotate 180 degree + move a group of shape using Processing?

I'm having this Processing issue where I'm trying to make my ariplane do a 180 turn-over right before it gets to the end of the drawing... It looks fairly simple but I just can't get it to work. Help would be much appreciated.
As I have other shapes (not showned in code below) I finally managed to use pushMatrix(); & popMatrix(); to only impact the airplane. Although rotating it brings me sideways.
int globalX = 0;
int globalY = 600;
int vitesse = 3;
void setup() {
size(800, 600);
rectMode(CENTER);
}
void draw() {
background(255);
pushMatrix();
bouger();
tourner();
dessinerAvion(globalX, globalY);
popMatrix();
}
void bouger() {
// Change the x location by vitesse
//globalX = globalX + vitesse;
globalY = globalY - vitesse;
}
void tourner() {
if (globalY < 0) {
for (int i = 10; i > 0; i = i-1) {
rotate(PI/3.0);
//rotate(radians(10));
}
for (int i = 10; i > 0; i = i-1) {
rotate(PI/3.0);
//rotate(radians(10));
vitesse = vitesse * -1;
}
}
}
void dessinerAvion(int x, int y) {
rectMode(CENTER);
// corps
fill(156,21,21);
ellipse(x + 250, y + 100, 50, 100);
rect(x + 250, y + 200, 50, 250,60);
// reacteur gauche
rect(x + 125, y + 225, 25, 50, 75);
// reacteur droite
rect(x + 375, y + 225, 25, 50, 75);
fill(210,14,14);
// aile gauche
quad(x + 75, y + 275, x + 75, y + 250, x + 225, y + 175, x + 225, y + 250);
// aile droite
quad(x + 425, y + 275, x + 425, y + 250, x + 275, y + 175, x + 275, y + 250);
fill(112,14,14);
// ailette gauche
triangle(x + 225, y + 290, x + 226, y + 310, x + 175, y + 310);
// ailette droites
triangle(x + 275, y + 290, x + 274, y + 310, x + 325, y + 310);
// aile verticale arrière
triangle(x + 250, y + 285, x + 248, y + 310, x + 252, y + 310);
}
Matrix are weird animals. They let you take a bunch of shapes and translate, rotate or scale them all at once, but only through playing with the coordinate system, which implies that the way you draw the shapes that you'll process will have a huge impact on how they react.
This is your plane on a neutral grid (by which I mean that globalX and globalY are 0):
Every square on this grid has a 20 pixels side. As you can see, your plane is far from the [0, 0] point, which is the start of the coordinate system it's drawn into. Also, when you modify it's coordinates, you're doing it through modifying it's coordinates, as we can see on this line of code:
void dessinerAvion(int x, int y)
The problem here is that applying a geometric transformation to an object with these coordinates can be counterintuitive. To simplify the matter, imagine the whole coordinate system that the plane use as if it was an image, like a png. The top left of the image is the [0, 0] point. When you change the plane's y coordinates for a higher number, your png image gets taller. If you change the x coordinate of the plane for a bigger number, the png image gets wider. You get the general idea.
When you apply a rotation to the plane, it's the entire 'png image', the entire coordinate system, that gets rotated. It's the same for any other transformation, too. Now, experimentally, look at what happens if I do this:
for(float i=0; i<20; i+=1) {
pushMatrix();
rotate(PI/i);
dessinerAvion(0, 0);
popMatrix();
}
As you can see, the plane isn't rotating on itself. It's the whole coordinate system that rotates, pivoting on the [0, 0] point, and it brings the plane with itself. That's the deal with matrix: they keep a lot of geometry waaay more simple, but you have to work accordingly.
Which brings me back to the previous point: you're updating the coordinates where you draw the plane. Then you use a matrix. Your updated plane will rotate in an unexpected way because you apply 2 different logic to your transformations: on one hand you modify coordinates (which is fine), while on the other you apply a transformation on a matrix (which is fine too). The issue is to mix those without acknowledging their differences.
Honestly, unless you have a specific thing in mind, you shouldn't mix those to move a single object.
The obvious solution to the first part of this issue would be to translate the plane instead of moving it's coordinates:
void draw() {
background(255);
bouger(); // updating coordinates, this doesn't need to be in the matrix block
pushMatrix();
//tourner();
translate(globalX, globalY);
dessinerAvion(0, 0); // zero here as the translation is doing that part of the job now
popMatrix();
}
Now you should see... well, you should see no difference at all. That's normal. The real difference is not in how the sketch appears, but how it's processed behind the scenes. Now your plane is a static object on which we apply geometric transformations.
Now to the other part of your problem: animating an object. Well, to be honest the animation part is quite easy, but there's a lot of work to do beforehand, more precisely to fix the plane's original coordinates so they are easier to manage. Let me explain:
If you look at the first image where I show the plane without any geometric transformation, you'll notice that the left wing starts at about ~75 pixels from the 0 point, and that the nose of the plane is ~45 pixels away from the 0 on the y axis. Currently, that's the rotation's anchor point. If you want to rotate the plane in a gracious manner, you'll have to fix this. There are many ways to proceed, follow me.
Take notes, because for some reason very few people will explain in simple words how to rotate on an anchor point using a matrix.
To rotate a shape on a specific anchor point, you have to proceed as follow:
pushMatrix
translate to the shape's destination
rotate
draw the shape using [0, 0] as it's anchor point
popMatrix
So if you want to rotate your plane on itself, thus deciding on an angle then moving it to it's destination before you draw the plane, you act as follow:
void draw() {
background(255);
// grid
stroke(0);
for (int i=0; i<height; i+=20) {
line(0, i, width, i);
}
for (int i=0; i<width; i+=20) {
line(i, 0, i, height);
}
pushMatrix();
translate(200, 200); // for the demonstration only
globalAngle--;
rotate(radians(globalAngle)); // I'm lazy and I don't want to do this in radians so I use degrees and convert them
dessinerAvion(-250, -200); // the center of the plane is the [250, 200] point. That's my anchor point.
popMatrix();
}
Which gives us this result:
Now, using the first translate, you can draw the plane wherever you want, and you can use the rotation to make it face any direction you want it to face.
Using these, it would be real easy to make the plane cross the screen back and forth:
void draw() {
background(255);
drawGrid();
// implementing the "u-turn"
globalY -= vitesse;
if (globalY < -200) {
globalX -= 200;
globalAngle = 180;
vitesse = -vitesse;
}
// drawing the sketch
pushMatrix();
translate(globalX, globalY);
rotate(radians(globalAngle));
dessinerAvion(-250, -200);
popMatrix();
}
Now, if we want to watch the plane turn, we have several options. You can rotate the plane frame by frame while plotting it's course. That's a good option. But I'm lazy, as previously stated, so instead I'll move the anchor point somewhere left of the plane and use it to rotate around it in a gracious manner:
void draw() {
background(255);
drawGrid();
pushMatrix();
translate(globalX, globalY);
tourner();
bouger();
dessinerAvion(0, -200); // this coordinate sets my anchor point
popMatrix();
}
void tourner() {
if (globalY < 300) { // starting the u-turn
vitesse = 0; // The plane won't move while it's rotating... except for the rotation itself
globalAngle--; // rotating counterclockwise 1 degree per frame
if (globalAngle<-180) { // u-turn completed
globalAngle = -180;
vitesse = -3;
}
}
rotate(radians(globalAngle));
}
I think that does it. I hope this helps. I'll lurk around in case you have questions about this answer.
Have fun!

jCanvas method drawRect() position issue

I have a problem with jCanvas which is a framework drawing canvas and jquery.
When I set x = 0 , y = 0 The .drawRect output should be same as the pure javascript result.
I tried the document and google but I did not find anything to solve this.
Screen shoot:
Pure js result
jCanvas result
Please help me.
Many thanks.
// Pure javascript
var canvas = document.getElementsByTagName('canvas')[0];
var ctx = canvas.getContext("2d");
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 100, 100);
// jCanvas
$('canvas').drawRect({
fillStyle: '#000',
x: 0, y: 0,
width: 100,
height: 100
});
To run this code, please copy and pass to this sanbox.
https://projects.calebevans.me/jcanvas/sandbox/
From jCanvas documentation:
The [.drawRect's] fromCenter property determines if a rectangle’s x and y properties lie at its center (as opposed to its top-left corner). This property is true by default.
So yeah. Your X and Y with jCanvas, refer to the center of the rectangle, unlike canvas' fillRect X and Y, which refer to the top left corner of the rectangle.

Processing - Ellipse is covering text

In the below image, I would like to have the score as text within the green ellipse. However, the ellipse is being drawn over the text, regardless of the order of the ellipse() and text() functions in the loop. Can anyone suggest why? My draw loop is shown below.
import processing.core.PApplet;
import processing.core.PFont;
public void drawHUD(PApplet marker, Clock time, int score)
{
PFont font = marker.createFont("Impact", 25, true);
marker.textFont(font);
marker.ellipseMode(CENTER);
fill(25, 100, 25);
marker.ellipse(50, marker.height - 50, 75, 50);
marker.noFill();
marker.text("Score: ", 25, marker.height - 100);
marker.text(score, 50, marker.height - 50);
marker.text("Seconds left: ", marker.width - 175, marker.height - 100);
marker.text(time.toString(), marker.width - 125, marker.height - 50);
}
Try replacing marker.noFill(); with marker.fill(255);. I don't believe noFill() works for text, and I have a feeling the text is being drawn above the ellipse, but is just the same colour as the ellipse so can't be seen.
I deleted the ellipsemode. It got rid of the center ellipse, but it got the text to the front.

Getting RMagick/ImageMagick gravity with text

Here is Ruby code:
require 'rmagick'
include Magick
img = Image.new(300, 300)
draw = Draw.new
draw.line(0, 150, 300, 150)
draw.line(150, 0, 150, 300)
# for each of known gravity constants...
%w[NorthWestGravity NorthGravity NorthEastGravity WestGravity CenterGravity EastGravity SouthWestGravity SouthGravity SouthEastGravity].
each{|g|
# set gravity to this value...
draw.gravity Magick.const_get(g)
# ...and draw text with this constant name
draw.text 150, 150, g
}
draw.draw(img)
img.write('tmp/gravity.png')
Here is image which it produces:
For SouthEast/NorthWest and similar gravities result is as expected (text is near 150,150, moved in desired direction). But for South, North and others result is really pretty weird.
As far as I can understand from code, RMagick just translates gravity and text commands into corresponding ImageMagick drawing primitives, so, I suppose its something in ImageMagick's gravity concept that I can't get.
What is it?..
I suppose its something in ImageMagick's gravity concept that I can't get.
What is it?..
The key to understanding what's going on is to locate the CenterGravity text.
Shifted left by 150px, and down by 150px.
Now compare compare NorthWestGravity position.
Also translated left & down by 150px respectively. Seeing a trend?
Your issue is with this line...
draw.text 150, 150, g
The Magick::Draw API maps to MVG spec. Use Magick::Draw.push & Magick::Draw.pop to control drawing context.
Edit from comments...
For setting the origin of text to be drawing, you'll need to calculate the position after evaluation the text/type metrics.
Example.
require 'rmagick'
include Magick
img = Image.new(300, 300) {
self.background_color = "palegreen"
}
draw = Draw.new
dotes = Draw.new # Dotes added for point of origin
dotes.fill = "red"
cursor = 1
# for each of known gravity constants...
%w[NorthWestGravity NorthGravity NorthEastGravity WestGravity CenterGravity EastGravity SouthWestGravity SouthGravity SouthEastGravity].
each{|g|
offsetX = 150
offsetY = cursor * 25
dotes.circle offsetX, offsetY, offsetX+2, offsetY+2
# Get metrics of text
metrics = draw.get_type_metrics(img, g)
# Full width
if %w[NorthWestGravity WestGravity SouthWestGravity].include? g then
offsetX -= metrics[:width]
end
# Full height
if %w[SouthWestGravity SouthGravity SouthEastGravity].include? g then
offsetY += metrics[:ascent]
end
# Half width
if %w[NorthGravity SouthGravity CenterGravity].include? g then
offsetX -= metrics[:width] / 2
end
# Half height
if %w[WestGravity CenterGravity EastGravity].include? g then
offsetY += metrics[:ascent] / 2
end
draw.text offsetX, offsetY, g
cursor += 1
}
dotes.draw(img)
draw.draw(img)
img.write('output.png')

JQuery SVG plugin: get space cordinate at page position

I've created an SVG canvas inline in HTML, dynamically using keith-wood jquery plugin.
I've used the viewBox attribute in the SVG element to scale the contents of the SVG canvas.
Inside the SVG canvas, I have a rectangle drawn, with a repeated pattern (tiles), each a 1x1 unit.
An 800x800 board:
var svg = $(containerDiv).svg('get');
svg.configure({viewBox: minX + "" + minY + "" + width + "" + height});
var pat1 = svg.pattern('pat1', 0, 0, 1, 1, {patternUnits: 'userSpaceOnUse'});
var rectTile = svg.rect(pat1, 0, 0, 1, 1);
var rectBoard = svg.rect(0, 0, 800, 800, {fill: 'url(#pat1)'});
When the user hovers over one of the elements inside the SVG, I'd like to convert the mouse event's pageX and pageY pixel coordinates to the coordinates of the tile, given the viewBox scaling. So if the user hovers over the first tile, I'd like {x: 0, y: 0} returned, given e.pageX and e.pageY where e is the mousemove jquery event object.
How do I calculate this?
Thanks

Resources