'use strict'; const parser = require("../parser"); const findAlignment = require("../parser/find-alignment"); const generateStyle = require("../parser/generate-style"); const measureText = require("../render/measure-text"); const adjustLineHeights = require("./adjust-line-heights"); const sum = require("../util/sum"); const min = require("../util/min"); const max = require("../util/max"); const last = require("../util/last"); function drawDebugLine(context, lineNumber, y, width, color = "red") { //context.globalAlpha = 1; context.strokeStyle = color; context.beginPath(); if (typeof width === "number") { context.moveTo(0, y); context.lineTo(width, y); } else { context.moveTo(width[0], y); context.lineTo(width[1], y); } context.stroke(); let boxOffsets = ["red", "green", "blue", "orange"]; let boxOffset = boxOffsets.indexOf(color); context.fillStyle = color; context.fillRect(boxOffset * 18, y + 3, 16, 16); context.font = "12px sans-serif"; context.fillStyle = "white"; context.fillText(lineNumber, 5 + boxOffset * 18, y + 15) //context.globalAlpha = 1; } module.exports = function layoutFormattedText(text, options) { if (options.tags === true) { let lines = parser(text).map((lineItems) => { let measuredItems = lineItems.filter(item => item.text !== "").map((item) => { let generatedStyle = generateStyle(item, { classes: options.classes, defaultStyle: options.defaultStyle }); return Object.assign({ measurements: measureText(item.text, generatedStyle), style: generatedStyle }, item); }); if (measuredItems.length > 0) { return { alignment: (lineItems.length > 0) ? findAlignment(lineItems[0]) : null, items: measuredItems, height: max(measuredItems.map(item => item.measurements.height)), width: sum(measuredItems.map(item => item.measurements.width)), minAscender: min(measuredItems.map(item => item.measurements.ascender)), maxDescender: max(measuredItems.map(item => item.measurements.descender)) } } else { /* This line does not contain any real elements, so we'll omit it. */ return null; } }).filter(line => line != null); let adjustedLineHeights = adjustLineHeights(lines, options.lineHeight); /* We start out by moving everything up half the first line's height - this * compensates for the middle-of-the-line-oriented line spacing of the first * line. Otherwise, everything would be shifted off the canvas.*/ let startHeightCorrection, endHeightCorrection; if (options.trimVerticalWhitespace) { startHeightCorrection = -(adjustedLineHeights[0] / 2) - (lines[0].minAscender / 2); endHeightCorrection = -(last(adjustedLineHeights) / 2) - (last(lines).minAscender / 2); } else { startHeightCorrection = 0; endHeightCorrection = 0; } let currentLineOffset = startHeightCorrection; let debugLineOffset = 0; let currentComponentOffset = 0; let debugLines = []; let positionedItems = lines.reduce((items, line, i) => { debugLines.push({color: "red", y: debugLineOffset, lineNumber: i}); debugLines.push({color: "green", y: currentLineOffset, lineNumber: i}); debugLines.push({color: "orange", y: currentLineOffset + adjustedLineHeights[i], lineNumber: i}); let newItems = items.concat(line.items.map((item) => { let positionedItem = Object.assign({ x: currentComponentOffset, y: currentLineOffset - (line.minAscender / 2) + (adjustedLineHeights[i] / 2) }, item); debugLines.push({color: "blue", y: positionedItem.y, x1: positionedItem.x, x2: positionedItem.x + item.measurements.width, lineNumber: i}); currentComponentOffset += item.measurements.width; return positionedItem; })); currentLineOffset += adjustedLineHeights[i]; debugLineOffset += adjustedLineHeights[i]; currentComponentOffset = 0; return newItems; }, []); return { width: Math.ceil(max(lines.map(line => line.width))), height: Math.ceil(sum(adjustedLineHeights) + startHeightCorrection + endHeightCorrection + (last(lines).maxDescender)), items: positionedItems, drawDebugLines: function drawDebugLines(context) { debugLines.forEach((debugLine) => { let width; if (debugLine.x1 != null) { width = [debugLine.x1, debugLine.x2]; } else { width = this.width; } drawDebugLine(context, debugLine.lineNumber, debugLine.y, width, debugLine.color); }); } } } else { let debugLines = []; let lines = text.split("\n").map((line) => { return { text: line, measurements: measureText(line, options.defaultStyle) }; }); let adjustedLineHeights = adjustLineHeights(lines.map((line) => { return { text: line.text, height: line.measurements.height } }), options.lineHeight); let startHeightCorrection, endHeightCorrection; if (options.trimVerticalWhitespace) { startHeightCorrection = -(adjustedLineHeights[0] / 2) - (lines[0].measurements.ascender / 2); endHeightCorrection = -(last(adjustedLineHeights) / 2) - (last(lines).measurements.ascender / 2); } else { startHeightCorrection = 0; endHeightCorrection = 0; } let currentLineOffset = startHeightCorrection; let debugLineOffset = 0; let positionedItems = lines.map((line, i) => { let newItem = { text: line.text, measurements: line.measurements, style: options.defaultStyle, x: 0, y: currentLineOffset - (line.measurements.ascender / 2) + (adjustedLineHeights[i] / 2) } debugLines.push({color: "red", y: debugLineOffset, lineNumber: i}); debugLines.push({color: "green", y: currentLineOffset, lineNumber: i}); debugLines.push({color: "blue", y: newItem.y, lineNumber: i}); debugLines.push({color: "orange", y: currentLineOffset + adjustedLineHeights[i], lineNumber: i}); currentLineOffset += adjustedLineHeights[i]; debugLineOffset += adjustedLineHeights[i]; return newItem; }); return { width: Math.ceil(max(lines.map(line => line.measurements.width))), height: Math.ceil(sum(adjustedLineHeights) + startHeightCorrection + endHeightCorrection + (last(lines).measurements.descender)), items: positionedItems, drawDebugLines: function drawDebugLines(context) { debugLines.forEach((debugLine) => { drawDebugLine(context, debugLine.lineNumber, debugLine.y, this.width, debugLine.color); }); } } } };