How to add images or texts sequentially by calculating the y cordinates in jspdf
Here is the demo code
I could not find any method that can help me calculate the Y cordinate of last image so that I can add next image or text after it
doc.text('Something written over here', 15, 18);
doc.addImage(base64_img, 10, 20, 0, 0); // should be added with y position based on text that ends above it.
doc.addImage(base64_img, 10, 40, 0, 0); // < 40 should be some val that should be next value of Y after 1st image
doc.addImage(base64_img, 10, 70, 0, 0);
You could try to 'play' with the size of the chars and the lenght of your string, something like this:
const yourText = 'Something written over here';
const sizeChar = 20;
const textWith = yourText.length * sizeChar;
doc.setFontSize(sizeChar);
doc.text(yourText, 15, 18);
doc.addImage(base64_img, 10, textWith, 0, 0);
Related
I want to check if there is a point in an area of the sprite
But the containsPoint method does not include the area and checks it as full.
const box = Sprite.from(box2Texture)
box.anchor.set(0.5, 0.5)
box.position.set(50, 25)
box.scale.set(0.3)
box.hitArea = new Polygon([-50, 0, 0, -25, 50, 0, 0, 25])
I'm trying to draw some kind of a "ray" with Phaser 3.
I started the experiment with a simple rectangle to represent the ray. Here is the code.
this.add.circle(90, 290, 10, 0xf00000);
this.add.circle(290, 190, 10, 0xf00000);
let angle = Phaser.Math.Angle.Between(90, 290, 290, 190)
let reference = this.add.rectangle(90, 290, 600, 5, 0x00f000).setOrigin(0);
reference.rotation = angle
The line doesn't start at the center of its starting point.
I know the reason is Phaser draws the line (actually rectangle) starting top-left 90, 290 where the centerline of the rectangle is supposed to start at.
To fix it, I just need to change y of the top-left
let reference = this.add.rectangle(90, 290-5/2, 600, 5, 0x00f000).setOrigin(0);
where the 5 in 5/2 represents the height of the green rectangle.
And then I got what I want
However, things got complicated when I try to move something along the "ray".
Here is the code.
var game = new Phaser.Game({
scene: {
preload: preload,
create: create
}
});
function preload() {
this.load.path = 'https://raw.githubusercontent.com/liyi93319/phaser3_rpg/main/part1/assets/';
this.load.atlas('bolt', 'bolt_atlas.png', 'bolt_atlas.json');
}
function create() {
this.add.circle(90, 290, 10, 0xf00000);
this.add.circle(290, 190, 10, 0xf00000);
let angle = Phaser.Math.Angle.Between(90, 290, 290, 190)
let reference = this.add.rectangle(90, 290 - 5 / 2, 600, 5, 0x00f000).setOrigin(0);
reference.rotation = angle
let bolt = this.add.sprite(90, 290-76/2, 'bolt', 'bolt_strike_0002').setOrigin(0);
bolt.rotation = angle;
}
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
I just added 2 lines of code based on the previously working code, with the same idea
let bolt = this.add.sprite(90, 290-76/2, 'bolt', 'bolt_strike_0002').setOrigin(0);
bolt.rotation = angle;
the 76 in 76/2 represents the height of the bolt (image/rectangle) I put.
290 represents the y coordinate of the center of the starting point.
The exactly same idea puts the green rectangle along the centerline of it though cannot put the bolt in the right place (along the centerline of the green rectangle), why is that? What am I missing?
To position the Image / rectangle, ... phaser uses the origin.
Fastes way to solve your problem is to use setOrigin(0, .5)
(Details to Phasers Origin )
Here The parameters explained:
the first paramater is the x - value: 0 = 0% = left
the second paramater is the y - value: 0.5 = 50% = middle
Info: If you set only one, like setOrigin(0), both x and y are set to 0.
Here the updated example:
var game = new Phaser.Game({
scene: {
preload: preload,
create: create
}
});
function preload() {
this.load.path = 'https://raw.githubusercontent.com/liyi93319/phaser3_rpg/main/part1/assets/';
this.load.atlas('bolt', 'bolt_atlas.png', 'bolt_atlas.json');
}
function create() {
this.add.circle(90, 290, 10, 0xf00000);
this.add.circle(290, 190, 10, 0xf00000);
let angle = Phaser.Math.Angle.Between(90, 290, 290, 190)
let reference = this.add.rectangle(90, 290 , 600, 5, 0x00f000).setOrigin(0, .5);
reference.rotation = angle
let bolt = this.add.sprite(90, 290, 'bolt', 'bolt_strike_0002').setOrigin(0, .5);
bolt.rotation = angle;
}
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
Info: In this Answer: https://stackoverflow.com/a/70803036/1679286, you can see how a different set origin alteres the effect of the scale property.
You need an offset which with change its position in local coordinates and the medium of any object is (width/2,height/2) given origin is at top left.
what I'm doing is using jsPDF to create a PDF of the graph I generated. However, I am not sure how to wrap the title (added by using the text() function). The length of the title will vary from graph to graph. Currently, my titles are running off the page. Any help would be appreciated!
This is the code i have so far:
var doc = new jsPDF();
doc.setFontSize(18);
doc.text(15, 15, reportTitle);
doc.addImage(outputURL, 'JPEG', 15, 40, 180, 100);
doc.save(reportTitle);
Nothing to keep the reportTitle from running off the page
Okay I've solved this. I used the jsPDF function, splitTextToSize(text, maxlen, options). This function returns an array of strings. Fortunately, the jsPDF text() function, which is used to write to the document, accepts both strings and arrays of strings.
var splitTitle = doc.splitTextToSize(reportTitle, 180);
doc.text(15, 20, splitTitle);
You can just use the optional argument maxWidth from the text function.
doc.text(15, 15, reportTitle, { maxWidth: 40 });
That will split the text once it reaches the maxWidth and start on the next line.
Auto-paging and text wrap issue in JSPDF can achieve with following code
var splitTitle = doc.splitTextToSize($('#textarea').val(), 270);
var pageHeight = doc.internal.pageSize.height;
doc.setFontType("normal");
doc.setFontSize("11");
var y = 7;
for (var i = 0; i < splitTitle.length; i++) {
if (y > 280) {
y = 10;
doc.addPage();
}
doc.text(15, y, splitTitle[i]);
y = y + 7;
}
doc.save('my.pdf');
To wrap long string of text to page use this code:
var line = 25 // Line height to start text at
var lineHeight = 5
var leftMargin = 20
var wrapWidth = 180
var longString = 'Long text string goes here'
var splitText = doc.splitTextToSize(longString, wrapWidth)
for (var i = 0, length = splitText.length; i < length; i++) {
// loop thru each line and increase
doc.text(splitText[i], leftMargin, line)
line = lineHeight + line
}
If you need to dynamically add new lines you want to access the array returned by doc.splitTextToSize and then add more vertical space as you go through each line:
var y = 0, lengthOfPage = 500, text = [a bunch of text elements];
//looping thru each text item
for(var i = 0, textlength = text.length ; i < textlength ; i++) {
var splitTitle = doc.splitTextToSize(text[i], lengthOfPage);
//loop thru each line and output while increasing the vertical space
for(var c = 0, stlength = splitTitle.length ; c < stlength ; c++){
doc.text(y, 20, splitTitle[c]);
y = y + 10;
}
}
Working Helper function
Here's a complete helper function based on the answers by #KB1788 and #user3749946:
It includes line wrap, page wrap, and some styling control:
(Gist available here)
function addWrappedText({text, textWidth, doc, fontSize = 10, fontType = 'normal', lineSpacing = 7, xPosition = 10, initialYPosition = 10, pageWrapInitialYPosition = 10}) {
var textLines = doc.splitTextToSize(text, textWidth); // Split the text into lines
var pageHeight = doc.internal.pageSize.height; // Get page height, well use this for auto-paging
doc.setFontType(fontType);
doc.setFontSize(fontSize);
var cursorY = initialYPosition;
textLines.forEach(lineText => {
if (cursorY > pageHeight) { // Auto-paging
doc.addPage();
cursorY = pageWrapInitialYPosition;
}
doc.text(xPosition, cursorY, lineText);
cursorY += lineSpacing;
})
}
Usage
// All values are jsPDF global units (default unit type is `px`)
const doc = new jsPDF();
addWrappedText({
text: "'Twas brillig, and the slithy toves...", // Put a really long string here
textWidth: 220,
doc,
// Optional
fontSize: '12',
fontType: 'normal',
lineSpacing: 7, // Space between lines
xPosition: 10, // Text offset from left of document
initialYPosition: 30, // Initial offset from top of document; set based on prior objects in document
pageWrapInitialYPosition: 10 // Initial offset from top of document when page-wrapping
});
When we use linebreak in jsPDF we get an error stating b.match is not defined, to solve this error just unminify the js and replace b.match with String(b).match and u will get this error twice just replace both and then we get c.split is not defined just do the same in this case replace it with String(c).match and we are done. Now you can see line breaks in you pdf. Thank you
I'm trying to make a function that returns x or y translate position of an element based on what pos was specified 'x' or 'y' like this
function getTranslate(el, pos) {
var translate = '',
position = 0;
if(el.css('transform')) {
translate = el.css('transform');
}
else if(el.css('-webkit-transform')) {
translate = el.css('-webkit-transform');
} else {
translate = 'translate3d(0, 0, 0)';
}
if(pos == 'x') {
position = /* Get x position here */ ;
}
else if(pos == 'y') {
position = /* Get y position here */ ;
}
}
When we grab transform property a string like 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 3, 3, 3, 1)' (more info here) is returned for translate3d(3px, 3px, 0). this gets stored in translate variable that I need to parse in order to get position variable to just store number value of either x or y position based on what was given to the function. I'm having trouble figuring out how to parse that string
you can parse the translate string with regular expressions:
var regex = /translate3d\(\s*([^ ,]+)\s*,\s*([^ ,]+)\s*,\s*([^ )]+)\s*\)/;
var test = "translate3d( 10px,20px , 30px)"
var result = test.split(regex);
alert('x is '+result[1]+", and y is "+result[2]);
You can get the position of an element with .offset() or .position().
They are different one get position relative to the document and second relative to the offset parent.
position = element.offset();
position = element.position();
http://api.jquery.com/offset/
https://api.jquery.com/position/
I'm trying to create an array of shapes that overlap. But I'm having difficulty preventing those shapes stacking on top of one-another.
I guess I want them to mesh together, if that makes sense?
Here's the code:
var overlap_canvas = document.getElementById("overlap");
var overlap_context = overlap_canvas.getContext("2d");
var x = 200;
var y = x;
var rectQTY = 4 // Number of rectangles
overlap_context.translate(x,y);
for (j=0;j<rectQTY;j++){ // Repeat for the number of rectangles
// Draw a rectangle
overlap_context.beginPath();
overlap_context.rect(-90, -100, 180, 80);
overlap_context.fillStyle = 'yellow';
overlap_context.fill();
overlap_context.lineWidth = 7;
overlap_context.strokeStyle = 'blue';
overlap_context.stroke();
// Degrees to rotate for next position
overlap_context.rotate((Math.PI/180)*360/rectQTY);
}
And here's my jsFiddle:
http://jsfiddle.net/Q8yjP/
And here's what I'm trying to achieve:
Any help or guidance would be greatly appreciated!
You cannot specify this behavior but you can implement an algorithmic-ish approach that uses composite modes.
As shown in this demo the result will be like this:
Define line width and the rectangles you want to draw (you can fill this array with the loop you already got to calculate the positions/angles - for simplicity I just use hard-coded ones here):
var lw = 4,
rects = [
[20, 15, 200, 75],
[150, 20, 75, 200],
[20, 150, 200, 75],
[15, 20, 75, 200]
], ...
I'll explain the line width below.
/// set line-width to half the size
ctx.lineWidth = lw * 0.5;
In the loop you add one criteria for the first draw which is also where you change composite mode. We also clear the canvas with the last rectangle:
/// loop through the array with rectangles
for(;r = rects[i]; i++) {
ctx.beginPath();
ctx.rect(r[0], r[1], r[2], r[3]);
ctx.fill();
ctx.stroke();
/// if first we do a clear with last rectangle and
/// then change composite mode and line width
if (i === 0) {
r = rects[rects.length - 1];
ctx.clearRect(r[0] - lw * 0.5, r[1] - lw * 0.5, r[2] + lw, r[3] + lw);
ctx.lineWidth = lw;
ctx.globalCompositeOperation = 'destination-over';
}
}
This will draw the rectangles and you have the flexibility to change the sizes without needing to recalculate clipping.
The line-width is set separately as stroke strokes the line from the middle. Therefor, since we later use destination-over mode it means half of the line won't be visible as we first fill which becomes part of destination so that the stroke will only be able to fill outside the stroked area (you could reverse the order of stroke and fill but will always run into an adjustment for the first rectangle).
We also need it to calculate the clipping which must include (half) the line on the outside.
This is also why we initially set it to half as the whole line will be drawn the first time - otherwise the first rectangle will have double as thick borders.
The only way to do it to cut your rectangles and compute which sub rectangle goes over which one. But I think you will have to draw your borders and inner rectangles separately because separating rectangles will add additional borders.
Hope it helped
Sadly, the feature you want of setting z-indexes on part of an element using canvas is not available currently. If you just need it for the four rectangle object you could do something like this which hides part of the rectangle to fake the effect you want, however this is hard coded to only 4 rectangles.
var overlap_canvas = document.getElementById("overlap");
var overlap_context = overlap_canvas.getContext("2d");
var x = 200;
var y = x;
var rectQTY = 4 // Number of rectangles
overlap_context.translate(x, y);
for (j = 0; j < rectQTY; j++) { // Repeat for the number of rectangles
// Draw a rectangle
overlap_context.beginPath();
overlap_context.rect(-90, -100, 180, 80);
overlap_context.fillStyle = 'yellow';
overlap_context.fill();
overlap_context.lineWidth = 7;
overlap_context.strokeStyle = 'blue';
overlap_context.stroke();
if (j === 3) {
overlap_context.beginPath();
overlap_context.rect(24, -86, 72, 80);
overlap_context.fillStyle = 'yellow';
overlap_context.fill();
overlap_context.closePath();
overlap_context.beginPath();
overlap_context.moveTo(20, -89.5);
overlap_context.lineTo(100, -89.5);
overlap_context.stroke();
overlap_context.closePath();
overlap_context.beginPath();
overlap_context.moveTo(20.5, -93.1);
overlap_context.lineTo(20.5, 23);
overlap_context.stroke();
overlap_context.closePath();
}
// Degrees to rotate for next position
overlap_context.rotate((Math.PI / 180) * 360 / rectQTY);
}
Demo here
If you have to make it dynamic, you could cut the shapes like Dark Duck suggested or you could try to create a function that detects when an object is overlapped and redraw it one time per rectangle (hard to do and not sure if it'd work). Perhaps you could come up with some equation for positioning the elements in relation to how I have them hard coded now to always work depending on the rotation angle, this would be your best bet IMO, but I don't know how to make that happen exactly
Overall you can't really do what you're looking for at this point in time
Using pure JavaScript ...
<!DOCTYPE html>
<html>
<head></head>
<body>
<canvas id="mycanvas" width="400px" height="400px"></canvas>
<script>
window.onload = function(){
var canvas = document.getElementById('mycanvas');
var ctx = canvas.getContext('2d');
//cheat - use a hidden canvas
var hidden = document.createElement('canvas');
hidden.width = 400;
hidden.height = 400;
var hiddenCtx = hidden.getContext('2d');
hiddenCtx.strokeStyle = 'blue';
hiddenCtx.fillStyle = 'yellow';
hiddenCtx.lineWidth = 5;
//translate origin to centre of hidden canvas, and draw 3/4 of the image
hiddenCtx.translate(200,200);
for(var i=0; i<3; i++){
hiddenCtx.fillRect(-170, -150, 300, 120);
hiddenCtx.strokeRect(-170, -150, 300, 120);
hiddenCtx.rotate(90*(Math.PI/180));
}
//reset the hidden canvas to original status
hiddenCtx.rotate(90*(Math.PI/180));
hiddenCtx.translate(-200,-200);
//translate to middle of visible canvas
ctx.translate(200, 200);
//repeat trick, this time copying from hidden to visible canvas
ctx.drawImage(hidden, 200, 0, 200, 400, 0, -200, 200, 400);
ctx.rotate(180*(Math.PI/180));
ctx.drawImage(hidden, 200, 0, 200, 400, 0, -200, 200, 400);
};
</script>
</body>
</html>
Demo on jsFiddle