From d918802596dd65c49266d0efaf1fa6a381cfc186 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Sun, 8 Dec 2024 15:04:02 -0500 Subject: [PATCH] Fix some alignment issues --- preview/index.html | 9 ++- src/type/p5.Font.js | 10 +++ src/type/text2d.js | 192 +++++++++++++++++++++++++++++--------------- 3 files changed, 141 insertions(+), 70 deletions(-) diff --git a/preview/index.html b/preview/index.html index e049faf722..8390b6e72f 100644 --- a/preview/index.html +++ b/preview/index.html @@ -25,7 +25,7 @@ p.setup = async function () { // TODO: make this work without a name - f = await p.loadFont('font/BricolageGrotesque-Variable.ttf', 'Bricolage') + f = await p.loadFont('font/Lato-Black.ttf', 'Lato') p.createCanvas(200, 200, testWebgl ? p.WEBGL : undefined); }; @@ -34,15 +34,16 @@ if (testWebgl) p.translate(-p.width/2, -p.height/2); p.fill('white'); - p.textSize(80); - p.textAlign(p.LEFT, p.TOP) + p.textSize(60); + p.textAlign(p.RIGHT, p.CENTER) p.textFont(f) - p.text('hello, world!', 10, 30, p.width); + p.text('hello, world!', 0, p.height/2, p.width); }; }; new p5(sketch); +

hello, world!

\ No newline at end of file diff --git a/src/type/p5.Font.js b/src/type/p5.Font.js index aaf5f02264..b039cd84f2 100644 --- a/src/type/p5.Font.js +++ b/src/type/p5.Font.js @@ -53,6 +53,16 @@ function font(p5, fn) { this.face = fontFace; } + verticalAlign(size) { + const { sCapHeight } = this.data?.['OS/2'] || {}; + const { unitsPerEm = 1000 } = this.data?.head || {}; + const { ascender = 0, descender = 0 } = this.data?.hhea || {}; + const current = ascender / 2; + const target = (sCapHeight || (ascender + descender)) / 2; + const offset = target - current; + return offset * size / unitsPerEm; + } + variations() { let vars = {}; if (this.data) { diff --git a/src/type/text2d.js b/src/type/text2d.js index e8e83186a5..23e9c49d89 100644 --- a/src/type/text2d.js +++ b/src/type/text2d.js @@ -762,38 +762,6 @@ function text2d(p5, fn) { // return { x, y, w, h }; // }; - /* - Position the lines of text based on their textAlign/textBaseline properties - */ - Renderer.prototype._positionLines = function (x, y, width, height, lines) { - - let { textLeading, textAlign } = this.states; - let adjustedX, lineData = new Array(lines.length); - let adjustedW = typeof width === 'undefined' ? 0 : width; - let adjustedH = typeof height === 'undefined' ? 0 : height; - - for (let i = 0; i < lines.length; i++) { - switch (textAlign) { - case fn.START: - throw new Error('textBounds: START not yet supported for textAlign'); // default to LEFT - case fn.LEFT: - adjustedX = x; - break; - case fn.CENTER: - adjustedX = x + adjustedW / 2; - break; - case fn.RIGHT: - adjustedX = x + adjustedW; - break; - case fn.END: // TODO: add fn.END: - throw new Error('textBounds: END not yet supported for textAlign'); - } - lineData[i] = { text: lines[i], x: adjustedX, y: y + i * textLeading }; - } - - return this._yAlignOffset(lineData, adjustedH); - }; - /* Process the text string to handle line-breaks and text wrapping @param {string} str - the text to process @@ -860,40 +828,6 @@ function text2d(p5, fn) { } } - /* - Get the y-offset for text given the height, leading, line-count and textBaseline property - */ - Renderer.prototype._yAlignOffset = function (dataArr, height) { - - if (typeof height === 'undefined') { - throw Error('_yAlignOffset: height is required'); - } - - let { textLeading, textBaseline } = this.states; - let yOff = 0, numLines = dataArr.length; - let ydiff = height - (textLeading * (numLines - 1)); - switch (textBaseline) { // drawingContext ? - case fn.TOP: - break; // ?? - case fn.BASELINE: - break; - case fn._CTX_MIDDLE: - yOff = ydiff / 2; - break; - case fn.BOTTOM: - yOff = ydiff; - break; - case fn.IDEOGRAPHIC: - console.warn('textBounds: IDEOGRAPHIC not yet supported for textBaseline'); // FES? - break; - case fn.HANGING: - console.warn('textBounds: HANGING not yet supported for textBaseline'); // FES? - break; - } - dataArr.forEach(ele => ele.y += yOff); - return dataArr; - } - /* Align the bounding box based on the current rectMode setting */ @@ -1179,6 +1113,72 @@ function text2d(p5, fn) { this.pop(); }; + + /* + Position the lines of text based on their textAlign/textBaseline properties + */ + p5.Renderer2D.prototype._positionLines = function (x, y, width, height, lines) { + + let { textLeading, textAlign } = this.states; + let adjustedX, lineData = new Array(lines.length); + let adjustedW = typeof width === 'undefined' ? 0 : width; + let adjustedH = typeof height === 'undefined' ? 0 : height; + + for (let i = 0; i < lines.length; i++) { + switch (textAlign) { + case fn.START: + throw new Error('textBounds: START not yet supported for textAlign'); // default to LEFT + case fn.LEFT: + adjustedX = x; + break; + case fn.CENTER: + adjustedX = x + adjustedW / 2; + break; + case fn.RIGHT: + adjustedX = x + adjustedW; + break; + case fn.END: // TODO: add fn.END: + throw new Error('textBounds: END not yet supported for textAlign'); + } + lineData[i] = { text: lines[i], x: adjustedX, y: y + i * textLeading }; + } + + return this._yAlignOffset(lineData, adjustedH); + }; + + /* + Get the y-offset for text given the height, leading, line-count and textBaseline property + */ + p5.Renderer2D.prototype._yAlignOffset = function (dataArr, height) { + + if (typeof height === 'undefined') { + throw Error('_yAlignOffset: height is required'); + } + + let { textLeading, textBaseline } = this.states; + let yOff = 0, numLines = dataArr.length; + let ydiff = height - (textLeading * (numLines - 1)); + switch (textBaseline) { // drawingContext ? + case fn.TOP: + break; // ?? + case fn.BASELINE: + break; + case fn._CTX_MIDDLE: + yOff = ydiff / 2; + break; + case fn.BOTTOM: + yOff = ydiff; + break; + case fn.IDEOGRAPHIC: + console.warn('textBounds: IDEOGRAPHIC not yet supported for textBaseline'); // FES? + break; + case fn.HANGING: + console.warn('textBounds: HANGING not yet supported for textBaseline'); // FES? + break; + } + dataArr.forEach(ele => ele.y += yOff); + return dataArr; + } } if (p5.RendererGL) { p5.RendererGL.prototype.textDrawingContext = function() { @@ -1190,6 +1190,66 @@ function text2d(p5, fn) { } return this._textDrawingContext; }; + + p5.RendererGL.prototype._positionLines = function (x, y, width, height, lines) { + + let { textLeading, textAlign } = this.states; + const widths = lines.map((line) => this._fontWidthSingle(line)); + let adjustedX, lineData = new Array(lines.length); + let adjustedW = typeof width === 'undefined' ? Math.max(0, ...widths) : width; + let adjustedH = typeof height === 'undefined' ? 0 : height; + + for (let i = 0; i < lines.length; i++) { + switch (textAlign) { + case fn.START: + throw new Error('textBounds: START not yet supported for textAlign'); // default to LEFT + case fn.LEFT: + adjustedX = x; + break; + case fn._CTX_MIDDLE: + adjustedX = x + (adjustedW - widths[i]) / 2; + break; + case fn.RIGHT: + adjustedX = x + adjustedW - widths[i]; + break; + case fn.END: // TODO: add fn.END: + throw new Error('textBounds: END not yet supported for textAlign'); + } + lineData[i] = { text: lines[i], x: adjustedX, y: y + i * textLeading }; + } + + return this._yAlignOffset(lineData, adjustedH); + }; + + p5.RendererGL.prototype._yAlignOffset = function (dataArr, height) { + + if (typeof height === 'undefined') { + throw Error('_yAlignOffset: height is required'); + } + + let { textLeading, textBaseline, textSize, textFont } = this.states; + let yOff = 0, numLines = dataArr.length; + let totalHeight = textSize * numLines + ((textLeading - textSize) * (numLines - 1)); + switch (textBaseline) { // drawingContext ? + case fn.TOP: + yOff = textSize; + break; + case fn.BASELINE: + break; + case fn._CTX_MIDDLE: + yOff = -totalHeight / 2 + textSize; + break; + case fn.BOTTOM: + yOff = -(totalHeight - textSize); + break; + default: + console.warn(`${textBaseline} is not supported in WebGL mode.`); // FES? + break; + } + yOff += this.states.textFont.font?.verticalAlign(textSize) || 0; + dataArr.forEach(ele => ele.y += yOff); + return dataArr; + } } }