Skip to content
Snippets Groups Projects
Select Git revision
  • 07a873702a303d8ccfd028e8279df5bd3b993580
  • master default protected
2 results

left-corner-slogan.js

Blame
  • user avatar
    Tomáš Hozman authored
    07a87370
    History
    left-corner-slogan.js 35.55 KiB
    class LeftCornerSloganTemplate extends Template {
    	description = "Určeno pro sociální sítě.";
    	
    	changeableAttributes = [
    		"logoImage",
    		"primaryImage",
    		"additionalPrimaryImages3",
    		"primaryText",
    		"primaryColorScheme",
    		"primaryImagePosition",
    		"secondaryText",
    	];
    
    	secondaryText = "";
    	
    	additionalPrimaryImages = [
    		null,
    		null,
    		null
    	]
    	
    	defaultResolution = 2000;
    	aspectRatio = 1;
    
    	primaryColorSchemes = [
    		"black-on-white",
    		"white-on-black"
    	];
    	
    	changeableColors = [
    		"primaryTextColor",
    		"foregroundColor",
    		"backgroundColor",
    		"secondaryTextColor",
    		"primaryTextHighlightColor",
    		"requesterTextColor"
    	];
    
    	// Canvas
    	async redrawCanvas() {
    		if (this.redrawing) {
    			return;
    		}
    
    		this.redrawing = true;
    		
    		const backgroundRectangleWidth = Math.ceil(this.canvas.width * 0.4);
    		let backgroundRectanglePaddingInner = Math.ceil(this.canvas.height * 0.025);
    		
    		const foregroundRectangleAngle = Math.ceil(this.canvas.height * 0.013);
    		const foregroundRectanglePaddingLeft = Math.ceil(this.canvas.width * 0.06);
    		const foregroundRectanglePaddingRight = Math.ceil(this.canvas.width * 0.035);
    		const foregroundRectanglePaddingTopBottom = Math.ceil(this.canvas.width * 0.035);
    		const foregroundRectangleWidth = Math.ceil(this.canvas.width * 0.6);
    		
    		const highlightPaddingSides = Math.ceil(this.canvas.width * 0.01);
    		const highlightPaddingBottom = Math.ceil(this.canvas.height * 0.0125);
    		const highlightPaddingTop = Math.ceil(this.canvas.height * -0.01);
    		
    		let primaryFontSize = Math.ceil(this.canvas.height * 0.1);
    		let primaryTextMaxLines = 3;
    		
    		const logoWidth = Math.ceil(this.canvas.width * 0.275) * this.logoImageZoom;
    		const logoOffsetBottom = Math.ceil(this.canvas.height * 0.065) * ((3 - this.logoImageZoom) / 2);
    		const logoOffsetSide = Math.ceil(this.canvas.width * 0.065);
    		
    		let secondaryFontSize = Math.ceil(this.canvas.height * 0.042);
    		let secondaryTextMaxLines = 3;
    		const secondaryFontLinePadding = 0;
    		
    		// Clear the canvas
    		this.context.clearRect(
    			0, 0,
    			this.canvas.width, this.canvas.height
    		);
    		
    		let additionalPrimaryImageCount = 0;
    		
    		for (const image of this.additionalPrimaryImages) {
    			if (image !== null) {
    				additionalPrimaryImageCount++;
    			}
    		}
    		
    		let skipOverlayDraw = false;
    		
    		let primaryLines = null;
    		const originalPrimaryFontSize = primaryFontSize;
    		
    		do {
    			this.context.font = `${this.primaryFontStyle} ${primaryFontSize}px ${this.primaryFont}`;
    			
    			primaryLines = splitStringIntoLines(
    				this.context,
    				this.primaryText,
    				foregroundRectangleWidth - foregroundRectanglePaddingLeft - foregroundRectanglePaddingRight,
    				primaryTextMaxLines,
    				true
    			).reverse();
    			
    			if (
    				primaryLines.length > primaryTextMaxLines
    				&& (
    					primaryLines.length * primaryFontSize
    					> primaryTextMaxLines * originalPrimaryFontSize
    				)
    			) {
    				primaryFontSize -= 2;
    			}
    		} while (
    			primaryLines.length > primaryTextMaxLines
    			&& (
    				primaryLines.length * primaryFontSize
    				> primaryTextMaxLines * originalPrimaryFontSize
    			)
    		);
    		
    		let secondaryTextLines = null;
    		const originalSecondaryFontSize = secondaryFontSize;
    		
    		do {
    			this.context.font = `${this.primaryFontStyle} ${secondaryFontSize}px ${this.primaryFont}`;
    			
    			secondaryTextLines = splitStringIntoLines(
    				this.context,
    				this.secondaryText,
    				backgroundRectangleWidth,
    				secondaryTextMaxLines,
    				true
    			);
    			
    			if (
    				secondaryTextLines.length > secondaryTextMaxLines
    				&& (
    					secondaryTextLines.length * (secondaryFontSize + secondaryFontLinePadding)
    					> secondaryTextMaxLines * (originalSecondaryFontSize + secondaryFontLinePadding)
    				)
    			) {
    				secondaryFontSize -= 2;
    			}
    		} while (
    			secondaryTextLines.length > secondaryTextMaxLines
    			&& (
    				secondaryTextLines.length * (secondaryFontSize + secondaryFontLinePadding)
    				> secondaryTextMaxLines * (originalSecondaryFontSize + secondaryFontLinePadding)
    			)
    		);
    		
    		const foregroundRectangleHeight = (
    			primaryLines.length * primaryFontSize
    			+ 2 * foregroundRectanglePaddingTopBottom
    		);
    		
    		const classRef = this;
    		
    		// Set image
    		if (this.primaryImage !== null) {
    			if (additionalPrimaryImageCount === 0) {
    				// https://github.com/DonkeyDushan/piratilol/blob/main/src/js/index.js
    				// Thanks to DonkeyDushan, the guy who made the joke 2021 campaign generator :D
    				
    				const imageScaleX = this.canvas.width / this.primaryImage.width;
    				const imageScaleY = this.canvas.height / this.primaryImage.height;
    				
    				const imageScale = Math.max(imageScaleX, imageScaleY) * this.primaryImageZoom;
    				
    				// https://stackoverflow.com/a/8529655
    				// Thanks to alex!
    				this.context.setTransform(
    					imageScale,
    					0, 0,
    					imageScale,
    					(this.canvas.width - this.primaryImage.width * imageScale) / 2 + this.primaryImageX * this.primaryImageZoom,
    					(this.canvas.height - this.primaryImage.height * imageScale) / 2 + this.primaryImageY * this.primaryImageZoom,
    				);
    				this.context.drawImage(
    					this.primaryImage,
    					0, 0
    				);
    				this.context.setTransform(); // Reset transformation
    			} else {
    				switch (additionalPrimaryImageCount) {
    					case 1: {
    						const halfCanvasWidth = this.canvas.width / 2;
    						
    						// Primary image
    						let primaryImageWidth = (this.primaryImage.width * (this.canvas.height / this.primaryImage.height));
    						const primaryImageWidthScale = this.primaryImage.width / primaryImageWidth;
    						
    						if (primaryImageWidth < halfCanvasWidth) {
    							primaryImageWidth = halfCanvasWidth;
    						}
    						
    						const primaryImageCropWidth = (
    							(primaryImageWidth > halfCanvasWidth) ?
    							(
    								primaryImageWidth
    								- halfCanvasWidth
    							) / 2
    							: 0
    						) * primaryImageWidthScale;
    						
    						this.context.drawImage(
    							this.primaryImage,
    							primaryImageCropWidth, 0,
    							(
    								primaryImageWidth
    								* primaryImageWidthScale
    								- primaryImageCropWidth * 2
    							), this.primaryImage.height,
    							0, 0,
    							 halfCanvasWidth, this.canvas.height
    						);
    						
    						// Additional image
    						let additionalImage = null;
    						
    						for (const image of this.additionalPrimaryImages) {
    							if (image !== null) {
    								additionalImage = image;
    							}
    						}
    						
    						let additionalImageWidth = (additionalImage.width * (this.canvas.height / additionalImage.height));
    						const additionalImageWidthScale = additionalImage.width / additionalImageWidth;
    						
    						if (additionalImageWidth < halfCanvasWidth) {
    							additionalImageWidth = halfCanvasWidth;
    						}
    						
    						const additionalImageCropWidth = (
    							(additionalImageWidth > halfCanvasWidth) ?
    							(
    								additionalImageWidth
    								- halfCanvasWidth
    							) / 2
    							: 0
    						) * additionalImageWidthScale;
    						
    						this.context.drawImage(
    							additionalImage,
    							additionalImageCropWidth, 0,
    							(
    								additionalImageWidth
    								* additionalImageWidthScale
    								- additionalImageCropWidth * 2
    							), additionalImage.height,
    							halfCanvasWidth, 0,
    							halfCanvasWidth, this.canvas.height
    						);
    						
    						break;
    					}
    					case 2: {
    						const halfCanvasWidth = this.canvas.width / 2;
    						
    						// Primary image
    						let primaryImageWidth = (this.primaryImage.width * (this.canvas.height / this.primaryImage.height));
    						const primaryImageWidthScale = this.primaryImage.width / primaryImageWidth;
    						
    						if (primaryImageWidth < halfCanvasWidth) {
    							primaryImageWidth = halfCanvasWidth;
    						}
    						
    						const primaryImageCropWidth = (
    							(primaryImageWidth > halfCanvasWidth) ?
    							(
    								primaryImageWidth
    								- halfCanvasWidth
    							) / 2
    							: 0
    						) * primaryImageWidthScale;
    						
    						this.context.drawImage(
    							this.primaryImage,
    							 primaryImageCropWidth, 0,
    							 (
    								 primaryImageWidth
    								 * primaryImageWidthScale
    								 - primaryImageCropWidth * 2
    							 ), this.primaryImage.height,
    							 0, 0,
    							 halfCanvasWidth, this.canvas.height
    						);
    						
    						// Additional images
    						let additionalImage1 = null;
    						let additionalImage2 = null;
    						
    						for (const image of this.additionalPrimaryImages) {
    							if (image !== null) {
    								if (additionalImage1 === null) {
    									additionalImage1 = image;
    								} else {
    									additionalImage2 = image;
    								}
    							}
    						}
    						
    						let additionalImageY = 0;
    						
    						for (const additionalImage of [additionalImage1, additionalImage2]) {
    							const helperCanvas = document.createElement("canvas");
    							helperCanvas.width = halfCanvasWidth;
    							helperCanvas.height = this.canvas.height / 2;
    							
    							const helperContext = helperCanvas.getContext("2d");
    							
    							// https://github.com/DonkeyDushan/piratilol/blob/main/src/js/index.js
    							// Thanks to DonkeyDushan, the guy who made the joke 2021 campaign generator :D
    							
    							const additionalImageScaleX = helperCanvas.width / additionalImage.width;
    							const additionalImageScaleY = helperCanvas.height / additionalImage.height;
    							const additionalImageScale = Math.max(additionalImageScaleX, additionalImageScaleY);
    							
    							helperContext.setTransform(
    								additionalImageScale,
    								0, 0,
    								additionalImageScale,
    								(helperCanvas.width - additionalImage.width * additionalImageScale) / 2,
    								(helperCanvas.height - additionalImage.height * additionalImageScale) / 2
    							);
    							
    							helperContext.drawImage(additionalImage, 0, 0);
    							helperContext.setTransform();
    							
    							this.context.drawImage(
    								helperCanvas,
    								halfCanvasWidth, additionalImageY,
    								halfCanvasWidth, this.canvas.height / 2
    							);
    							
    							additionalImageY += this.canvas.height / 2;
    						}
    						
    						break;
    					}
    					case 3: {
    						skipOverlayDraw = true;
    						
    						const halfCanvasWidth = this.canvas.width / 2;
    						const halfCanvasHeight = this.canvas.height / 2;
    						let imageX = 0;
    						let imageY = 0;
    						
    						for (const image of [
    							this.primaryImage,
    							this.additionalPrimaryImages[0],
    							this.additionalPrimaryImages[1],
    							this.additionalPrimaryImages[2]
    						]) {
    							const helperCanvas = document.createElement("canvas");
    							helperCanvas.width = halfCanvasWidth;
    							helperCanvas.height = halfCanvasHeight;
    							
    							const helperContext = helperCanvas.getContext("2d");
    							
    							// https://github.com/DonkeyDushan/piratilol/blob/main/src/js/index.js
    							// Thanks to DonkeyDushan, the guy who made the joke 2021 campaign generator :D
    							
    							const imageScaleX = helperCanvas.width / image.width;
    							const imageScaleY = helperCanvas.height / image.height;
    							const imageScale = Math.max(imageScaleX, imageScaleY);
    							
    							helperContext.setTransform(
    								imageScale,
    								0, 0,
    								imageScale,
    								(helperCanvas.width - image.width * imageScale) / 2,
    								(helperCanvas.height - image.height * imageScale) / 2
    							);
    							
    							helperContext.drawImage(image, 0, 0);
    							helperContext.setTransform();
    							
    							this.context.drawImage(
    								helperCanvas,
    								imageX, imageY,
    								halfCanvasWidth, halfCanvasHeight
    							);
    							
    							if (imageY !== halfCanvasHeight) {
    								imageY += halfCanvasHeight;
    							} else {
    								imageY = 0;
    								imageX += halfCanvasWidth;
    							}
    						}
    						
    						const helperCanvas = document.createElement("canvas");
    						helperCanvas.height = (
    							foregroundRectangleHeight
    							+ (
    								(this.secondaryText !== "") ?
    								(
    									(
    										secondaryTextLines.length
    										* (secondaryFontSize + secondaryFontLinePadding)
    									)
    									+ 2 * backgroundRectanglePaddingInner
    								) : 0
    							)
    						);
    						helperCanvas.width = foregroundRectangleWidth;
    						
    						const helperContext = helperCanvas.getContext("2d");
    						
    						if (this.secondaryText !== "") {
    							helperContext.font = `${this.primaryFontStyle} ${secondaryFontSize}px ${this.primaryFont}`;
    							
    							let currentSecondaryLineY = (
    								helperCanvas.height
    								- backgroundRectanglePaddingInner
    							);
    							
    							helperContext.fillStyle = this.foregroundColor;
    							
    							helperContext.fillRect(
    								0, (
    									helperCanvas.height
    									- (
    										(
    											secondaryTextLines.length
    											* (secondaryFontSize + secondaryFontLinePadding)
    										)
    										+ 2 * backgroundRectanglePaddingInner
    									)
    								),
    								(
    									backgroundRectangleWidth
    									+ 2 * backgroundRectanglePaddingInner
    								), (
    									(
    										secondaryTextLines.length
    										* (secondaryFontSize + secondaryFontLinePadding)
    									)
    									+ 2 * backgroundRectanglePaddingInner
    								)
    							);
    							
    							helperContext.fillStyle = this.primaryTextColor;
    							helperContext.textAlign = "right";
    							
    							for (const line of secondaryTextLines.reverse()) {
    								helperContext.fillText(
    									line.join(" "),
    									backgroundRectangleWidth + backgroundRectanglePaddingInner, 
    									currentSecondaryLineY
    								);
    								
    								currentSecondaryLineY -= secondaryFontSize;
    							}
    							
    							helperContext.textAlign = "left";
    						}
    						
    						helperContext.beginPath();
    			
    						helperContext.fillStyle = this.backgroundColor;
    						
    						helperContext.moveTo(
    							0,
    							foregroundRectangleAngle,
    						);
    						helperContext.lineTo(
    							0,
    							foregroundRectangleHeight + foregroundRectangleAngle
    						);
    						helperContext.lineTo(
    							foregroundRectangleWidth,
    							foregroundRectangleHeight
    						);
    						helperContext.lineTo(
    							foregroundRectangleWidth,
    							0
    						);
    						
    						helperContext.closePath();
    						
    						helperContext.fill();
    						
    						const foregroundRGB = hexToRgb(this.backgroundColor);
    						const foregroundLightness = (
    							0.2126 * foregroundRGB.r
    							+ 0.7152 * foregroundRGB.g
    							+ 0.0722 * foregroundRGB.b
    						);
    						const useLightHighlight = (foregroundLightness > 207);
    						
    						let currentPrimaryLineY = (
    							foregroundRectangleHeight
    							- backgroundRectanglePaddingInner * 1.5
    						);
    						
    						const primaryLineX = foregroundRectanglePaddingLeft;
    						
    						helperContext.font = `${this.primaryFontStyle} ${primaryFontSize}px ${this.primaryFont}`;
    						
    						let primaryTextHighlightedColor = null;
    						
    						const lowercasePrimaryTextHighlightColor = this.primaryTextHighlightColor.toLowerCase();
    						const hasColorOverride = (
    							lowercasePrimaryTextHighlightColor === "#209a37" ||
    							lowercasePrimaryTextHighlightColor === "#e63812"
    						);
    						
    						if (hasColorOverride) {
    							if (useLightHighlight) {
    								primaryTextHighlightedColor = this.foregroundColor;
    							} else {
    								primaryTextHighlightedColor = this.primaryTextColor;
    							}
    						} else if (!useLightHighlight) {
    							primaryTextHighlightedColor = this.foregroundColor;
    						} else {
    							primaryTextHighlightedColor = this.primaryTextColor;
    						}
    						
    						helperContext.fillStyle = this.secondaryTextColor;
    						
    						for (let line of primaryLines) {
    							let wordPosition = 0;
    							
    							for (let word of line) {
    								const previousWords = line.slice(0, wordPosition).join(" ");
    								const previousWordsWidth = helperContext.measureText(
    									previousWords
    									+ (
    										(previousWords.length !== 0) ?
    										" " : ""
    									)
    								).width;
    								
    								const nextWords = line.slice(wordPosition + 1, line.length).join(" ")
    								const nextWordsWidth = helperContext.measureText(
    									nextWords
    									+ (
    										(nextWords.length !== 0) ?
    										" " : ""
    									)
    								).width;
    								
    								let currentWordWidth = helperContext.measureText(word).width;
    								
    								for (const word of line.slice(wordPosition + 1, line.length)) {
    									if (word.isHighlighted) {
    										currentWordWidth += helperContext.measureText(word.toString() + " ").width;
    									} else {
    										break;
    									}
    								}
    								
    								if (word.isHighlighted) {
    									if (
    										wordPosition === 0 ||
    										!line[wordPosition - 1].isHighlighted
    									) {
    										helperContext.fillStyle = this.primaryTextHighlightColor;
    										helperContext.beginPath();
    										
    										const startingHighlightLineX = (
    											primaryLineX
    											+ previousWordsWidth
    										);
    										
    										helperContext.moveTo(
    											startingHighlightLineX - highlightPaddingSides,
    											currentPrimaryLineY + highlightPaddingBottom
    										);
    										helperContext.lineTo(
    											(
    												startingHighlightLineX
    												+ currentWordWidth
    												+ highlightPaddingSides
    											),
    											(
    												currentPrimaryLineY
    												+ highlightPaddingBottom
    												- Math.max(
    													(currentWordWidth * foregroundRectangleAngle)
    													/ foregroundRectangleWidth
    												)
    											)
    										);
    										helperContext.lineTo(
    											(
    												startingHighlightLineX
    												+ currentWordWidth
    												+ highlightPaddingSides
    											),
    											(
    												currentPrimaryLineY
    												- primaryFontSize
    												- highlightPaddingTop
    												- Math.max(
    													(currentWordWidth * foregroundRectangleAngle)
    													/ foregroundRectangleWidth
    												)
    											)
    										);
    										helperContext.lineTo(
    											startingHighlightLineX - highlightPaddingSides,
    											(
    												currentPrimaryLineY
    												- primaryFontSize
    												- highlightPaddingTop
    											)
    										);
    										
    										helperContext.closePath();
    										
    										helperContext.fill();
    									}
    									
    									helperContext.fillStyle = primaryTextHighlightedColor;
    								}
    								
    								helperContext.fillText(
    									word + " ",
    									primaryLineX + previousWordsWidth,
    									currentPrimaryLineY
    								);
    								
    								wordPosition++;
    								
    								helperContext.fillStyle = this.secondaryTextColor;
    							}
    							
    							currentPrimaryLineY -= primaryFontSize;
    						}
    						
    						this.context.shadowColor = "#000000";
    						this.context.shadowBlur = this.canvas.width / 8;
    						this.context.shadowOffsetX = 0;
    						this.context.shadowOffsetY = 0;
    						
    						this.context.drawImage(
    							helperCanvas,
    							(this.canvas.width - helperCanvas.width) / 2,
    							(this.canvas.height - helperCanvas.height) / 2
    						);
    						
    						const alternateLogoWidth = (
    							foregroundRectangleWidth
    							- backgroundRectangleWidth
    							- backgroundRectanglePaddingInner * 3
    						) * this.logoImageZoom;
    						
    						function drawLogoImage(image) {
    							let logoHeight = Math.ceil(image.height * (alternateLogoWidth / image.width));
    							
    							classRef.context.drawImage(
    								image,
    								(
    									classRef.canvas.width - (
    										(
    											classRef.canvas.width
    											- helperCanvas.width
    										) / 2
    										+ alternateLogoWidth
    									)
    								), (
    									classRef.canvas.height - (
    										classRef.canvas.height
    										- helperCanvas.height
    										+ (
    											(classRef.secondaryText !== "") ?
    											(
    												(
    													secondaryTextLines.length
    													* (secondaryFontSize + secondaryFontLinePadding)
    												)
    												+ 2 * backgroundRectanglePaddingInner
    												+ logoHeight
    											) : (-backgroundRectanglePaddingInner)
    										)
    									) / 2
    								),
    								alternateLogoWidth, logoHeight
    							);
    						}
    						
    						if (this.logoImage === null) {
    							const logoImageLoadPromise = new Promise(
    								resolve => {
    									let logoImage = new Image();
    									
    									logoImage.onload = function() {
    										drawLogoImage(this);
    										
    										resolve();
    									}
    									
    									logoImage.src = classRef.lightLogoDefaultSource;
    								}
    							);
    							
    							await logoImageLoadPromise;
    						} else {
    							drawLogoImage(this.logoImage);
    						}
    						
    						this.context.shadowColor = "rgba(0, 0, 0, 0)";
    						
    						break;
    					}
    				}
    			}
    		}
    		
    		if (!skipOverlayDraw) {
    			if (this.secondaryText !== "") {
    				let currentSecondaryLineY = (
    					this.canvas.height
    					- foregroundRectangleHeight
    					- foregroundRectangleAngle
    					- backgroundRectanglePaddingInner / 2
    				);
    				
    				this.context.fillStyle = this.backgroundColor;
    				
    				this.context.fillRect(
    					0, this.canvas.height - foregroundRectangleHeight - (
    						secondaryTextLines.length
    						* (secondaryFontSize + secondaryFontLinePadding)
    					) - 2 * backgroundRectanglePaddingInner,
    					(
    						backgroundRectangleWidth
    						+ 2 * backgroundRectanglePaddingInner
    					),
    					(
    						(
    							secondaryTextLines.length
    							* (secondaryFontSize + secondaryFontLinePadding)
    						)
    						+ 2 * backgroundRectanglePaddingInner
    						+ foregroundRectangleAngle
    					)
    				);
    				
    				this.context.fillStyle = this.secondaryTextColor;
    				this.context.textAlign = "right";
    				this.context.textBaseline = "bottom";
    				
    				for (const line of secondaryTextLines.reverse()) {
    					this.context.fillText(
    						line.join(" "),
    						backgroundRectangleWidth + backgroundRectanglePaddingInner,
    						currentSecondaryLineY
    					);
    					
    					currentSecondaryLineY -= secondaryFontSize;
    				}
    				
    				this.context.textBaseline = "alphabetic";
    				this.context.textAlign = "left";
    			}
    			
    			this.context.beginPath();
    			
    			this.context.fillStyle = this.foregroundColor;
    			
    			this.context.moveTo(
    				0,
    				this.canvas.height
    			);
    			this.context.lineTo(
    				0,
    				this.canvas.height - foregroundRectangleHeight
    			);
    			this.context.lineTo(
    				foregroundRectangleWidth,
    				this.canvas.height - foregroundRectangleHeight - foregroundRectangleAngle
    			);
    			this.context.lineTo(
    				foregroundRectangleWidth,
    				this.canvas.height - foregroundRectangleAngle
    			);
    			
    			this.context.closePath();
    			
    			this.context.fill();
    			
    			const foregroundRGB = hexToRgb(this.foregroundColor);
    			const foregroundLightness = (
    				0.2126 * foregroundRGB.r
    				+ 0.7152 * foregroundRGB.g
    				+ 0.0722 * foregroundRGB.b
    			);
    			const useLightHighlight = (foregroundLightness > 207);
    			
    			let currentPrimaryLineY = this.canvas.height - foregroundRectanglePaddingTopBottom - foregroundRectangleAngle;
    			
    			const primaryLineX = foregroundRectanglePaddingLeft;
    			
    			this.context.font = `${this.primaryFontStyle} ${primaryFontSize}px ${this.primaryFont}`;
    			
    			let primaryTextHighlightedColor = null;
    			
    			const lowercasePrimaryTextHighlightColor = this.primaryTextHighlightColor.toLowerCase();
    			const hasColorOverride = (
    				lowercasePrimaryTextHighlightColor === "#209a37" ||
    				lowercasePrimaryTextHighlightColor === "#e63812"
    			);
    		
    			if (hasColorOverride) {
    				if (useLightHighlight) {
    					primaryTextHighlightedColor = this.foregroundColor;
    				} else {
    					primaryTextHighlightedColor = this.primaryTextColor;
    				}
    			} else if (!useLightHighlight) {
    				primaryTextHighlightedColor = this.foregroundColor;
    			} else {
    				primaryTextHighlightedColor = this.primaryTextColor;
    			}
    			
    			this.context.fillStyle = this.primaryTextColor;
    			
    			for (let line of primaryLines) {
    				let wordPosition = 0;
    				
    				for (let word of line) {
    					const previousWords = line.slice(0, wordPosition).join(" ");
    					const previousWordsWidth = this.context.measureText(
    						previousWords
    						+ (
    							(previousWords.length !== 0) ?
    							" " : ""
    						)
    					).width;
    					
    					const nextWords = line.slice(wordPosition + 1, line.length).join(" ")
    					const nextWordsWidth = this.context.measureText(
    						nextWords
    						+ (
    							(nextWords.length !== 0) ?
    							" " : ""
    						)
    					).width;
    					
    					let currentWordWidth = this.context.measureText(word).width;
    					
    					for (const word of line.slice(wordPosition + 1, line.length)) {
    						if (word.isHighlighted) {
    							currentWordWidth += this.context.measureText(word.toString() + " ").width;
    						} else {
    							break;
    						}
    					}
    					
    					if (word.isHighlighted) {
    						if (
    							wordPosition === 0 ||
    							!line[wordPosition - 1].isHighlighted
    						) {
    							this.context.fillStyle = this.primaryTextHighlightColor;
    							this.context.beginPath();
    							
    							const startingHighlightLineX = (
    								primaryLineX
    								+ previousWordsWidth
    							);
    							
    							this.context.moveTo(
    								startingHighlightLineX - highlightPaddingSides,
    								currentPrimaryLineY + highlightPaddingBottom
    							);
    							this.context.lineTo(
    								(
    									startingHighlightLineX
    									+ currentWordWidth
    									+ highlightPaddingSides
    								),
    								(
    									currentPrimaryLineY
    									+ highlightPaddingBottom
    									- Math.max(
    										(currentWordWidth * foregroundRectangleAngle)
    										/ foregroundRectangleWidth
    									)
    								)
    							);
    							this.context.lineTo(
    								(
    									startingHighlightLineX
    									+ currentWordWidth
    									+ highlightPaddingSides
    								),
    								(
    									currentPrimaryLineY
    									- primaryFontSize
    									- highlightPaddingTop
    									- Math.max(
    										(currentWordWidth * foregroundRectangleAngle)
    										/ foregroundRectangleWidth
    									)
    								)
    							);
    							this.context.lineTo(
    								startingHighlightLineX - highlightPaddingSides,
    								(
    									currentPrimaryLineY
    									- primaryFontSize
    									- highlightPaddingTop
    								)
    							);
    							
    							this.context.closePath();
    							
    							this.context.fill();
    						}
    						
    						this.context.fillStyle = primaryTextHighlightedColor;
    					}
    					
    					this.context.fillText(
    						word + " ",
    						primaryLineX + previousWordsWidth,
    						currentPrimaryLineY
    					);
    					
    					wordPosition++;
    					
    					this.context.fillStyle = this.primaryTextColor;
    				}
    				
    				currentPrimaryLineY -= primaryFontSize;
    			}
    			
    			function drawLogoImage(image) {
    				let logoHeight = Math.ceil(image.height * (logoWidth / image.width));
    				
    				classRef.context.drawImage(
    					image,
    					classRef.canvas.width - logoWidth - logoOffsetSide, classRef.canvas.height - logoHeight - logoOffsetBottom,
    					logoWidth, logoHeight
    				);
    			}
    			
    			if (this.logoImage === null) {
    				const logoImageLoadPromise = new Promise(
    					resolve => {
    						let logoImage = new Image();
    						
    						logoImage.onload = function() {
    							drawLogoImage(this);
    							
    							resolve();
    						}
    						
    						logoImage.src = classRef.lightLogoDefaultSource;
    					}
    				);
    				
    				await logoImageLoadPromise;
    			} else {
    				drawLogoImage(this.logoImage);
    			}
    		}
    		
    		if (this.requesterText !== "") {
    			// https://newspaint.wordpress.com/2014/05/22/writing-rotated-text-on-a-javascript-canvas/
    			// Thanks to newspaint!
    			
    			this.context.save();
    
    			this.context.translate(this.canvas.width - 1, 0);
    
    			this.context.rotate(3 * Math.PI / 2);
    
    			let requesterFontSize = Math.ceil(this.canvas.width * 0.015);
    			
    			do {
    				this.context.font = `${this.primaryFontStyle} ${requesterFontSize}px ${this.primaryFont}`;
    				
    				if (
    					this.context.measureText(this.requesterText).width
    					> foregroundRectangleHeight
    				) {
    					requesterFontSize -= 2;
    					this.context.font = `${this.primaryFontStyle} ${requesterFontSize}px ${this.primaryFont}`;
    				}
    			} while (
    				this.context.measureText(this.requesterText).width
    				> foregroundRectangleHeight
    			);
    			
    			this.context.fillStyle = this.requesterTextColor;
    
    			this.context.textAlign = "left";
    
    			this.context.globalAlpha = 0.6;
    			this.context.fillText(
    				this.requesterText,
    				-this.canvas.height * 0.985, -this.canvas.width * 0.9925 + requesterFontSize
    			);
    			this.context.globalAlpha = 1;
    			
    			this.context.restore();
    		}
    		
    		this.finalDrawHook();
    		this.stickerDrawHook();
    		
    		this.redrawing = false;
    	}
    
    	// Additional primary images
    	async setAdditionalPrimaryImageFromInput(position, input, skipRedraw = false) {
    		const classRef = this;
    		
    		if (input.files.length == 0) {
    			classRef.additionalPrimaryImages[position] = null;
    			return;
    		}
    
    		const readPromise = new Promise(
    			resolve => {
    				const fileReader = new FileReader();
    
    				fileReader.onloadend = function(event) {
    					classRef.additionalPrimaryImages[position] = new Image();
    					
    					classRef.additionalPrimaryImages[position].onload = function() {
    						if (!skipRedraw) {
    							classRef.redrawCanvas();
    						}
    						
    						resolve();
    					}
    					
    					classRef.additionalPrimaryImages[position].src = event.target.result;
    				}
    
    				fileReader.readAsDataURL(input.files[0]);
    			}
    		);
    		
    		await readPromise;
    	}
    	
    	// Text
    	async setSecondaryText(text, skipRedraw = false) {
    		this.secondaryText = text;
    		
    		if (!skipRedraw) {
    			await this.redrawCanvas();
    		}
    	}
    
    	// Color schemes
    	async setPrimaryColorScheme(scheme, skipRedraw = false) {
    		switch (scheme) {
    			case "black-on-white":
    				this.primaryTextColor = "#000000";
    				this.foregroundColor = "#ffffff";
    				this.backgroundColor = "#000000";
    				this.secondaryTextColor = "#ffffff";
    				this.primaryTextHighlightColor = "#ffcc00";
    				
    				break;
    			case "white-on-black":
    				this.primaryTextColor = "#ffffff";
    				this.foregroundColor = "#000000";
    				this.backgroundColor = "#ffffff";
    				this.secondaryTextColor = "#000000";
    				// this.primaryTextHighlightColor = "#ffcc00";
    				this.underNameTextColor = "#ffffff";
    				
    				break;
    			case "forum-black-on-white":
    				this.primaryTextColor = "#000000";
    				this.foregroundColor = "#ffffff";
    				this.backgroundColor = "#962a51";
    				this.secondaryTextColor = "#ffffff";
    				this.primaryTextHighlightColor = "#ffcc00";
    				
    				break;
    			case "forum-white-on-purple":
    				this.primaryTextColor = "#ffffff";
    				this.foregroundColor = "#962a51";
    				this.backgroundColor = "#ffffff";
    				this.secondaryTextColor = "#000000";
    				// this.primaryTextHighlightColor = "#ffcc00";
    				
    				break;
    			case "zeleni-volary-bystrc-most-black-on-white":
    				this.primaryTextColor = "#000000";
    				this.foregroundColor = "#ffffff";
    				this.backgroundColor = "#00ad43";
    				this.secondaryTextColor = "#ffffff";
    				this.primaryTextHighlightColor = "#ffcc00";
    				
    				break;
    			case "zeleni-volary-bystrc-most-white-on-green":
    				this.primaryTextColor = "#ffffff";
    				this.foregroundColor = "#00ad43";
    				this.backgroundColor = "#ffffff";
    				this.secondaryTextColor = "#000000";
    				// this.primaryTextHighlightColor = "#ffcc00";
    				
    				break;
    			case "spolecne-s-piraty-black-on-white":
    				this.primaryTextColor = "#000000";
    				this.foregroundColor = "#ffffff";
    				this.backgroundColor = "#21274e";
    				this.secondaryTextColor = "#ffffff";
    				this.primaryTextHighlightColor = "#ffcc00";
    				
    				break;
    			case "spolecne-s-piraty-white-on-blue":
    				this.primaryTextColor = "#ffffff";
    				this.foregroundColor = "#21274e";
    				this.backgroundColor = "#ffffff";
    				this.secondaryTextColor = "#000000";
    				// this.primaryTextHighlightColor = "#ffcc00";
    				
    				break;
    			case "louny-spolecne-black-on-white":
    				this.primaryTextColor = "#000000";
    				this.foregroundColor = "#ffffff";
    				this.backgroundColor = "#3e2a5b";
    				this.secondaryTextColor = "#ffffff";
    				this.primaryTextHighlightColor = "#ffcc00";
    				
    				break;
    			case "louny-spolecne-white-on-purple":
    				this.primaryTextColor = "#ffffff";
    				this.foregroundColor = "#3e2a5b";
    				this.backgroundColor = "#ffffff";
    				this.secondaryTextColor = "#000000";
    				// this.primaryTextHighlightColor = "#ffcc00";
    				
    				break;
    			case "litomerice-blue-on-white":
    				this.primaryTextColor = "#123172";
    				this.foregroundColor = "#ffffff";
    				this.backgroundColor = "#123172";
    				this.secondaryTextColor = "#ffffff";
    				this.primaryTextHighlightColor = "#afe87e";
    				
    				break;
    			case "litomerice-white-on-blue":
    				this.primaryTextColor = "#ffffff";
    				this.foregroundColor = "#123172";
    				this.backgroundColor = "#ffffff";
    				this.secondaryTextColor = "#123172";
    				// this.primaryTextHighlightColor = "#ffcc00";
    				
    				break;
    			case "stranane-gray-on-yellow":
    				this.primaryTextColor = "#ffffff";
    				this.foregroundColor = "#4d4d4d";
    				this.backgroundColor = "#ffd500";
    				this.secondaryTextColor = "#4d4d4d";
    				// this.primaryTextHighlightColor = "#ffcc00";
    				
    				break;
    			case "stranane-yellow-on-white":
    				this.primaryTextColor = "#4d4d4d";
    				this.foregroundColor = "#ffd500";
    				this.backgroundColor = "#ffffff";
    				this.secondaryTextColor = "#4d4d4d";
    				this.primaryTextHighlightColor = "#4d4d4d";
    				
    				break;
    			case "stranane-white-on-yellow":
    				this.primaryTextColor = "#4d4d4d";
    				this.foregroundColor = "#ffffff";
    				this.backgroundColor = "#ffd500";
    				this.secondaryTextColor = "#4d4d4d";
    				this.primaryTextHighlightColor = "#ffd500";
    				
    				break;
    			case "prusanky-black-on-yellow":
    				this.primaryTextColor = "#ffffff";
    				this.foregroundColor = "#000000";
    				this.backgroundColor = "#ffd500";
    				this.secondaryTextColor = "#000000";
    				// this.primaryTextHighlightColor = "#ffcc00";
    				
    				break;
    			case "prusanky-yellow-on-white":
    				this.primaryTextColor = "#000000";
    				this.foregroundColor = "#ffd500";
    				this.backgroundColor = "#ffffff";
    				this.secondaryTextColor = "#000000";
    				this.primaryTextHighlightColor = "#000000";
    				
    				break;
    			case "prusanky-white-on-yellow":
    				this.primaryTextColor = "#000000";
    				this.foregroundColor = "#ffffff";
    				this.backgroundColor = "#ffd500";
    				this.secondaryTextColor = "#000000";
    				this.primaryTextHighlightColor = "#ffd500";
    				
    				break;
    			case "ujezd-green-on-white":
    				this.primaryTextColor = "#000000";
    				this.foregroundColor = "#8ed4a3";
    				this.backgroundColor = "#ffffff";
    				this.secondaryTextColor = "#000000";
    				this.primaryTextHighlightColor = "#ffdd55";
    				
    				break;
    			case "ujezd-white-on-green":
    				this.primaryTextColor = "#000000";
    				this.foregroundColor = "#ffffff";
    				this.backgroundColor = "#8ed4a3";
    				this.secondaryTextColor = "#000000";
    				this.primaryTextHighlightColor = "#ffdd55";
    				
    				break;
    			case "cssd-red-on-black":
    				this.primaryTextColor = "#ffffff";
    				this.foregroundColor = "#e63812";
    				this.backgroundColor = "#000000";
    				this.secondaryTextColor = "#ffffff";
    				
    				break;
    			case "cssd-black-on-red":
    				this.primaryTextColor = "#ffffff";
    				this.foregroundColor = "#000000";
    				this.backgroundColor = "#e63812";
    				this.secondaryTextColor = "#ffffff";
    				
    				break;
    			case "jilemnice-purple-on-black":
    				this.primaryTextColor = "#ffffff";
    				this.foregroundColor = "#6e1646";
    				this.backgroundColor = "#000000";
    				this.secondaryTextColor = "#ffffff";
    				
    				break;
    			case "jilemnice-black-on-purple":
    				this.primaryTextColor = "#ffffff";
    				this.foregroundColor = "#000000";
    				this.backgroundColor = "#6e1646";
    				this.secondaryTextColor = "#ffffff";
    				
    				break;
    			case "novarole-white-on-green":
    				this.primaryTextColor = "#000000";
    				this.foregroundColor = "#ffffff";
    				this.backgroundColor = "#a9ce2d";
    				this.secondaryTextColor = "#000000";
    				this.primaryTextHighlightColor = "#a9ce2d";
    				
    				break;
    			case "novarole-green-on-white":
    				this.primaryTextColor = "#000000";
    				this.foregroundColor = "#a9ce2d";
    				this.backgroundColor = "#ffffff";
    				this.secondaryTextColor = "#000000";
    				
    				this.primaryTextHighlightColor = "#ffcc00";
    				
    				break;
    			case "novarole-green-on-black":
    				this.primaryTextColor = "#000000";
    				this.foregroundColor = "#a9ce2d";
    				this.backgroundColor = "#000000";
    				this.secondaryTextColor = "#ffffff";
    				
    				this.primaryTextHighlightColor = "#ffcc00";
    				
    				break;
    			case "zeleni-melnik-yellow-name-rect":
    				await this.setPrimaryColorScheme("white-on-black", true);
    				
    				this.nameBackgroundColor = "#fde119";
    				this.nameTextColor = "#000000";
    				
    				break;
    			default:
    				throw new Error("This scheme does not exist.");
    				break;
    		}
    		
    		this.requesterTextColor = this.underNameTextColor;
    		
    		if (!skipRedraw) {
    			await this.redrawCanvas();
    		}
    	}
    }