div.js 5.82 KB
/**
 * 2D Vector.
 * Container for x and y coordinates. 
 */

class Vector {
	constructor(x,y) {
		this.x = x;
		this.y = y;
	}

	/**
	 * @param {Vector} vector
	 */
	addv(vector) {
		return new Vector(
			this.x + vector.x,
			this.y + vector.y
		);
	}

	/**
	 * @param {number} x
	 * @param {number} y
	 */
	addxy(x = 0,y = 0) {
		return new Vector(
			this.x + x,
			this.y + y
		);
	}

	/**
	 * @param {number} n
	 */
	mult(n) {
		return new Vector(
			this.x * n,
			this.y * n
		);
	}
}

class Line {

	/**
	 * @param {Vector} beg 
	 * @param {Vector} end 
	 */
	constructor(beg, end) {
		this.beg = beg;
		this.end = end;
	}

	/**
	 * @param {jsPDF} pdf
	 */
	draw(pdf) {
		pdf.line(
			this.beg.x,
			this.beg.y,
			this.end.x,
			this.end.y
		);
	}
}

class Text {

	/**
	 * Both arguments should be real coordinates and not a percentage based on parent element
	 * @param {string} text 
	 * @param {Vector} position
	 * @param {object} font 
	 */
	constructor(text, position, font = null, align = null) {
		this.text = text;
		this.position = position;
		this.font = font
		this.align = align
	}

	/**
	 * @param {jsPDF} pdf
	 */
	draw(pdf) {

		/** replace ? with 0s (temporary) **/
		if (this.text == "?") this.text = "0.000";

		if (this.font){
			pdf.setFont(this.font.font);
			pdf.setFontType(this.font.type);
			pdf.setFontSize(this.font.size);
		}

		if (this.align) {
			pdf.text(
				this.text,
				this.position.x,
				this.position.y,
				this.align
			);
		}
		else {
			pdf.text(
				this.text,
				this.position.x,
				this.position.y
			)
		}
	}
}

class Image {

	/**
	 * Both arguments should be real coordinates and not a percentage based on parent element
	 * @param {string} text 
	 * @param {Vector} position
	 * @param {object} font 
	 */
	constructor(pointer, position, size) {
		this.pointer = pointer;
		this.position = position;
		this.size = size
	}

	/**
	 * @param {jsPDF} pdf
	 */
	draw(pdf) {
		pdf.addImage(
			this.pointer,
			this.position.x,
			this.position.y,
			this.size.x,
			this.size.y
		);
	}
}

/**
 * This class does NOT regulate if its children go out of its cofinements.
 * It does NOT resize or realocate its children to fit, in any way.
 * Its only purpose is to provide percentage based relative coordinates.
 */
class Div {

	/**
	 * @param {Vector} position 
	 * @param {Vector} size 
	 * @param {bool} draw 
	 */
	constructor(position, size, draw = false, log = false) {
		this.position = position;
		this.size = size;

		this.drawOutline = draw;
		this.logChildren = log;

		this.children = [];

		const t = this;
		this.acceptedProperties = [
			{
				"prop" : "position",
				"func" : (v) => t._getCoordinates(v)
			},
			{
				"prop" : "size",
				"func" : (v) => t._getSize(v)
			},
			{
				"prop" : "beg",
				"func" : (v) => t._getCoordinates(v)
			},
			{
				"prop" : "end",
				"func" : (v) => t._getCoordinates(v)
			},
		];
	}

	/** Allows any position you want **/
	addAbsoluteChild(child) {
		if ("draw" in child === false)
			console.error("the object passed needs to have a 'draw' method that takes a jsPDF object");
		this.children.push(child);
		return this.children[this.children.length - 1];
	}
	
	/** Allows only positions inside the Div **/
	addRelativeChild(child) {
		if ("draw" in child === false)
			console.error("the object passed needs to have a 'draw' method that takes a jsPDF object");
		
		this.acceptedProperties.forEach( (p) => {
			if (p.prop in child)
				child[ p.prop ] = p.func( child[ p.prop ] );
		});

		this.children.push(child);
		return this.children[this.children.length - 1];
	}

	addAbsoluteChildren(children) {
		const pointers = [];
		children.forEach(c => pointers.push(this.addAbsoluteChild(c)));
		return pointers;
	}

	addRelativeChildren(children) {
		const pointers = [];
		children.forEach(c => pointers.push(this.addRelativeChild(c)));
		return pointers;
	}
	
	/** Allows any position you want relative to the position of this Div. Percentage based **/
	getAbsoluteVector(v) {
		return this.position.addxy(
			(this.size.x * v.x / 100),
			(this.size.y * v.y / 100)
		);
	}
	
	/**
	 * Compiles the true position coordinates based on percentage relative to parent element
	 * Assumes the elements grow to the right and down
	 * @param {Vector} v 
	 */
	_getCoordinates(v) {
		if (v.x < 0 || v.x > 100 || v.y < 0 || v.y > 100)
			console.error("position components need to be real numbers in the range [0;100]");

		return this.position.addxy(
			(this.size.x * v.x / 100),
			(this.size.y * v.y / 100)
		);
	}

	/**
	 * Compiles the size coordinates based on percentage relative to parent element
	 * Assumes the elements grow to the right and down
	 * @param {Vector} s 
	 */
	_getSize(s) {
		if (s.x < 0 || s.x > 100 || s.y < 0 || s.y > 100)
			console.error("position components need to be real numbers in the range [0;100]");

		return new Vector (
			(this.size.x * s.x / 100),
			(this.size.y * s.y / 100)
		);
	}

	draw(pdf) {

		/** Draw the confines of the div **/
		if (this.drawOutline) {
			/** Upper **/
			this.addAbsoluteChild(new Line(
				this.position,
				this.position.addxy(this.size.x, 0)
			));
			/** Lower **/
			this.addAbsoluteChild(new Line(
				this.position.addxy(0, this.size.y),
				this.position.addv(this.size)
			));
			/** Left **/
			this.addAbsoluteChild(new Line(
				this.position,
				this.position.addxy(0, this.size.y)
			));
			/** Right **/
			this.addAbsoluteChild(new Line(
				this.position.addxy(this.size.x, 0),
				this.position.addv(this.size)
			));
		}

		/** Log Children **/
		if (this.logChildren) {
			console.log(this.children);
		}

		/** Recursion **/
		this.children.forEach( child => child.draw(pdf));
	}
}

const Pos = {
	"beg": 0,
	"beg_margin": 5,
	"quarter": 25,
	"third": 33.33,
	"middle": 50,
	"two_thirds": 66.67,
	"three_quarters": 75,
	"end_margin": 95,
	"end": 100
};

function compile(tree, pdf) {
	tree.children.forEach(child => child.draw(pdf));
	return pdf;
}