Fix line height implementation and add a debugging view

master
Sven Slootweg 7 years ago
parent 4cdd2eaff6
commit 8f51e98b92

@ -8,7 +8,7 @@ const setTextStyles = require("./render/set-text-styles");
const measureText = require("./render/measure-text");
function getTextProperties(item) {
return objectPick(item, ["fontSize", "fontFamily", "fontStyle", "fontWeight"]);
return objectPick(item, ["fontSize", "fontFamily", "fontStyle", "fontWeight", "fillColor", "strokeColor"]);
}
module.exports = function createTextShape(options) {
@ -16,9 +16,9 @@ module.exports = function createTextShape(options) {
_layout: null,
_lines: null,
type: "text",
cacheBustingProperties: ["fillColor", "strokeColor", "strokeWidth", "text", "fontSize", "fontFamily", "fontStyle", "fontWeight"],
sizeBustingProperties: ["text", "fontSize", "fontFamily", "fontStyle", "fontWeight"],
fillColor: "red",
cacheBustingProperties: ["fillColor", "strokeColor", "strokeWidth", "text", "fontSize", "fontFamily", "fontStyle", "fontWeight", "trimVerticalWhitespace", "renderDebugLines", "renderDebugArea", "lineHeight", "tags"],
sizeBustingProperties: ["text", "fontSize", "fontFamily", "fontStyle", "fontWeight", "trimVerticalWhitespace", "renderDebugLines", "renderDebugArea", "lineHeight", "tags"],
fillColor: "black",
strokeColor: "red",
strokeWidth: 0,
fontFamily: "sans-serif",
@ -26,17 +26,27 @@ module.exports = function createTextShape(options) {
fontWeight: "normal",
lineHeight: 1.16,
onRender: function onRender(context) {
if (this.renderDebugArea) {
context.fillStyle = "silver";
context.fillRect(0, 0, this._layout.width, this._layout.height);
}
this._layout.items.forEach((item) => {
setTextStyles(context, item.style);
context.fillText(item.text, item.x, item.y);
});
if (this.renderDebugLines) {
this._layout.drawDebugLines(context);
}
},
onRecalculateSize: function onRecalculateSize() {
this._layout = layout(this.text, {
classes: options.classes,
defaultStyle: getTextProperties(this),
lineHeight: this.lineHeight,
tags: this.tags
tags: this.tags,
trimVerticalWhitespace: this.trimVerticalWhitespace
});
return {

@ -2,11 +2,6 @@
module.exports = function adjustLineHeights(lines, lineHeight) {
return lines.map((line, i) => {
if (i !== lines.length - 1) {
return line.height * lineHeight * 1.13;
} else {
/* The last line doesn't get a lineHeight multiplier... */
return line.height * 1.13;
}
return line.height * lineHeight * 1.13;
});
};

@ -9,11 +9,41 @@ 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.map((item) => {
let measuredItems = lineItems.filter(item => item.text !== "").map((item) => {
let generatedStyle = generateStyle(item, {
classes: options.classes,
defaultStyle: options.defaultStyle
@ -25,33 +55,61 @@ module.exports = function layoutFormattedText(text, options) {
}, item);
});
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))
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);
let currentLineOffset = 0;
/* 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) + (last(lines).maxDescender);
} 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
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;
@ -59,10 +117,25 @@ module.exports = function layoutFormattedText(text, options) {
return {
width: Math.ceil(max(lines.map(line => line.width))),
height: Math.ceil(sum(adjustedLineHeights)),
items: positionedItems
height: Math.ceil(sum(adjustedLineHeights) + startHeightCorrection + endHeightCorrection),
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,
@ -77,7 +150,19 @@ module.exports = function layoutFormattedText(text, options) {
}
}), options.lineHeight);
let currentLineOffset = 0;
let startHeightCorrection, endHeightCorrection;
if (options.trimVerticalWhitespace) {
startHeightCorrection = -(adjustedLineHeights[0] / 2) - (lines[0].measurements.ascender / 2);
endHeightCorrection = -(last(adjustedLineHeights) / 2) - (last(lines).measurements.ascender / 2) + (last(lines).measurements.descender);
} else {
startHeightCorrection = 0;
endHeightCorrection = 0;
}
let currentLineOffset = startHeightCorrection;
let debugLineOffset = 0;
let positionedItems = lines.map((line, i) => {
let newItem = {
@ -85,18 +170,29 @@ module.exports = function layoutFormattedText(text, options) {
measurements: line.measurements,
style: options.defaultStyle,
x: 0,
y: currentLineOffset - line.measurements.ascender
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)),
items: positionedItems
height: Math.ceil(sum(adjustedLineHeights) + startHeightCorrection + endHeightCorrection),
items: positionedItems,
drawDebugLines: function drawDebugLines(context) {
debugLines.forEach((debugLine) => {
drawDebugLine(context, debugLine.lineNumber, debugLine.y, this.width, debugLine.color);
});
}
}
}
};

@ -12,7 +12,7 @@ module.exports = function measureText(text, options) {
setTextStyles(context, options);
let fontMeasurements = measureFont(options.fontFamily);
let fontMeasurements = measureFont(options.fontFamily, {fontSize: 40});
return Object.assign(context.measureText(text), {
height: options.fontSize * (fontMeasurements.descender - fontMeasurements.topBounding),

@ -19,10 +19,6 @@ module.exports = function setTextStyles(context, options) {
context.font = fontSegments.filter(segment => (segment != null)).join(" ");
context.textBaseline = defaultValue(options.textBaseline, "alphabetic");
if (options.isStroke) {
context.strokeStyle = options.color;
} else {
context.fillStyle = options.color;
}
context.strokeStyle = options.strokeColor;
context.fillStyle = options.fillColor;
}

@ -0,0 +1,5 @@
'use strict';
module.exports = function lastItem(array) {
return array[array.length - 1];
};
Loading…
Cancel
Save