diff --git a/src/layout/adjust-line-heights.js b/src/layout/adjust-line-heights.js deleted file mode 100644 index 7ebb3d6..0000000 --- a/src/layout/adjust-line-heights.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -module.exports = function adjustLineHeights(lines, lineHeight) { - return lines.map((line, i) => { - return line.height * lineHeight * 1.13; - }); -}; diff --git a/src/layout/index.js b/src/layout/index.js index 38ec5c0..d0ca375 100644 --- a/src/layout/index.js +++ b/src/layout/index.js @@ -12,8 +12,6 @@ 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(); @@ -35,164 +33,129 @@ function drawDebugLine(context, lineNumber, y, width, color = "red") { context.font = "12px sans-serif"; context.fillStyle = "white"; - context.fillText(lineNumber, 5 + boxOffset * 18, y + 15) + context.fillText(lineNumber, 5 + boxOffset * 18, y + 15); +} + +function layoutItems(lines, options = {}) { + let currentYOffset = options.initialY; + let currentXOffset = 0; + let debugYOffset = 0; + let debugLines = []; + + let positionedItems = lines.reduce((items, line, i) => { + debugLines.push({color: "red", y: debugYOffset, lineNumber: i}); + debugLines.push({color: "green", y: currentYOffset, lineNumber: i}); + debugLines.push({color: "orange", y: currentYOffset + line.adjustedHeight, lineNumber: i}); + + let newItems = items.concat(line.items.map((item) => { + let x = currentXOffset; + let y = currentYOffset - (line.minAscender / 2) + (line.adjustedHeight / 2) + + debugLines.push({color: "blue", y: y, x1: x, x2: x + item.measurements.width, lineNumber: i}); + + currentXOffset += item.measurements.width; + + return Object.assign({ + x: x, + y: y + }, item); + })); + + currentYOffset += line.adjustedHeight; + debugYOffset += line.adjustedHeight; + currentXOffset = 0; + + return newItems; + }, []); - //context.globalAlpha = 1; + return { + positionedItems: positionedItems, + debugLines: debugLines + } } 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); - }); + let lines; - 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); - }); - } - } + if (options.tags === true) { + lines = parser(text).map((lineItems) => { + return { + items: lineItems + }; + }); } else { - let debugLines = []; + lines = text.split("\n").map((line) => { + let lineMeasurements = measureText(line, options.defaultStyle); - let lines = text.split("\n").map((line) => { return { - text: line, - measurements: measureText(line, options.defaultStyle) + items: [{ + text: line + }] }; }); + } - let adjustedLineHeights = adjustLineHeights(lines.map((line) => { - return { - text: line.text, - height: line.measurements.height + let processedLines = lines.map((line) => { + let processedItems = line.items.map((item) => { + let style; + + if (item.tags != null) { + style = Object.assign({}, options.defaultStyle, generateStyle(item.tags, options.classes)); + } else { + style = options.defaultStyle; } - }), options.lineHeight); - let startHeightCorrection, endHeightCorrection; + return Object.assign(item, { + style: style, + measurements: measureText(item.text, style) + }); + }); - 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 height = max(processedItems.map(item => item.measurements.height)); - let currentLineOffset = startHeightCorrection; + return Object.assign(line, { + alignment: (line.items.length > 0) ? findAlignment(line.items[0]) : null, + items: processedItems, + height: height, + adjustedHeight: height * options.lineHeight * 1.13, + width: sum(processedItems.map(item => item.measurements.width)), + minAscender: min(processedItems.map(item => item.measurements.ascender)), + maxDescender: max(processedItems.map(item => item.measurements.descender)) + }); + }); - let debugLineOffset = 0; + let startHeightCorrection, endHeightCorrection; - 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) - } + if (options.trimVerticalWhitespace) { + startHeightCorrection = -(lines[0].adjustedHeight / 2) - (lines[0].minAscender / 2); + endHeightCorrection = -(last(lines).adjustedHeight / 2) - (last(lines).minAscender / 2); + } else { + startHeightCorrection = 0; + endHeightCorrection = 0; + } - 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}); + let {positionedItems, debugLines} = layoutItems(lines, { + initialY: startHeightCorrection + }); - currentLineOffset += adjustedLineHeights[i]; - debugLineOffset += adjustedLineHeights[i]; + let combinedLineHeights = sum(lines.map(line => line.adjustedHeight)); - return newItem; - }); + return { + width: Math.ceil(max(lines.map(line => line.width))), + height: Math.ceil(combinedLineHeights + startHeightCorrection + endHeightCorrection + last(lines).maxDescender), + items: positionedItems, + drawDebugLines: function drawDebugLines(context) { + debugLines.forEach((debugLine) => { + let width; - 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); - }); - } + if (debugLine.x1 != null) { + width = [debugLine.x1, debugLine.x2]; + } else { + width = this.width; + } + + drawDebugLine(context, debugLine.lineNumber, debugLine.y, width, debugLine.color); + }); } } };