diff --git a/src/core/constants.js b/src/core/constants.js index c203106730..9813fee3f2 100644 --- a/src/core/constants.js +++ b/src/core/constants.js @@ -1072,6 +1072,12 @@ export const CHAR = 'CHAR'; * @final */ export const WORD = 'WORD'; +/** + * @typedef {'JUSTIFIED'} JUSTIFIED + * @property {JUSTIFIED} JUSTIFIED + * @final + */ +export const JUSTIFIED = 'JUSTIFIED'; // TYPOGRAPHY-INTERNAL export const _DEFAULT_TEXT_FILL = '#000000'; diff --git a/src/type/textCore.js b/src/type/textCore.js index 03348b6b64..99bd9bee4d 100644 --- a/src/type/textCore.js +++ b/src/type/textCore.js @@ -1501,7 +1501,10 @@ function textCore(p5, fn) { lines = this._positionLines(x, y, width, height, lines); // render each line at the adjusted position - lines.forEach(line => this._renderText(line.text, line.x, line.y)); + lines.forEach((line, index) => { + const isLastLine = index === lines.length - 1; + this._renderText(line.text, line.x, line.y, Infinity, -Infinity, width, isLastLine); + }); this.textDrawingContext().textBaseline = setBaseline; // restore baseline }; @@ -2526,7 +2529,7 @@ function textCore(p5, fn) { return this.drawingContext; }; - p5.Renderer2D.prototype._renderText = function (text, x, y, maxY, minY) { + p5.Renderer2D.prototype._renderText = function (text, x, y, maxY, minY, width, isLastLine) { let states = this.states; let context = this.textDrawingContext(); @@ -2536,6 +2539,46 @@ function textCore(p5, fn) { this.push(); + // Handle JUSTIFIED alignment + if (states.textAlign === fn.JUSTIFIED && typeof width !== 'undefined' && !isLastLine) { + const words = text.split(' '); + if (words.length > 1) { + const textWidth = this._textWidthSingle(text); + const spaceWidth = this._textWidthSingle(' '); + const totalGaps = words.length - 1; + const extraSpace = width - textWidth; + const spacePerGap = spaceWidth + (extraSpace / totalGaps); + + // Render each word with adjusted spacing + let currentX = x; + for (let i = 0; i < words.length; i++) { + const word = words[i]; + + // no stroke unless specified by user + if (states.strokeColor && states.strokeSet) { + context.strokeText(word, currentX, y); + } + + if (!this._clipping && states.fillColor) { + // if fill hasn't been set by user, use default text fill + if (!states.fillSet) { + this._setFill(DefaultFill); + } + context.fillText(word, currentX, y); + } + + currentX += this._textWidthSingle(word); + if (i < words.length - 1) { + currentX += spacePerGap; + } + } + + this.pop(); + return; + } + } + + // Default rendering for non-JUSTIFIED or last line // no stroke unless specified by user if (states.strokeColor && states.strokeSet) { context.strokeText(text, x, y); @@ -2580,6 +2623,9 @@ function textCore(p5, fn) { case fn.RIGHT: adjustedX = x + adjustedW; break; + case fn.JUSTIFIED: + adjustedX = x; // JUSTIFIED starts from left + break; case textCoreConstants.END: throw new Error('textBounds: END not yet supported for textAlign'); } diff --git a/test/manual-test-examples/type/justified-sketch.js b/test/manual-test-examples/type/justified-sketch.js new file mode 100644 index 0000000000..d869edf1d3 --- /dev/null +++ b/test/manual-test-examples/type/justified-sketch.js @@ -0,0 +1,61 @@ +function setup() { + createCanvas(800, 600); + background(255); + + let sampleText = 'This is a sample text that will be justified. The spacing between words will be adjusted to align both left and right edges.'; + let boxWidth = 300; + let boxHeight = 150; + + // Test 1: JUSTIFIED with WORD wrap + fill(0); + textSize(16); + textAlign(JUSTIFIED, TOP); + textWrap(WORD); + + stroke(200); + noFill(); + rect(50, 50, boxWidth, boxHeight); + + fill(0); + noStroke(); + text('JUSTIFIED + WORD wrap:', 50, 30); + text(sampleText, 50, 50, boxWidth, boxHeight); + + // Test 2: JUSTIFIED with CHAR wrap + stroke(200); + noFill(); + rect(450, 50, boxWidth, boxHeight); + + fill(0); + noStroke(); + textWrap(CHAR); + text('JUSTIFIED + CHAR wrap:', 450, 30); + text(sampleText, 450, 50, boxWidth, boxHeight); + + // Test 3: LEFT alignment for comparison + stroke(200); + noFill(); + rect(50, 250, boxWidth, boxHeight); + + fill(0); + noStroke(); + textAlign(LEFT, TOP); + textWrap(WORD); + text('LEFT + WORD wrap (comparison):', 50, 230); + text(sampleText, 50, 250, boxWidth, boxHeight); + + // Test 4: Show last line is ragged + let multiLineText = 'First line will be justified. Second line will also be justified. But the last line stays ragged.'; + + stroke(200); + noFill(); + rect(450, 250, boxWidth, boxHeight); + + fill(0); + noStroke(); + textAlign(JUSTIFIED, TOP); + text('JUSTIFIED - last line ragged:', 450, 230); + text(multiLineText, 450, 250, boxWidth, boxHeight); + + noLoop(); +} diff --git a/test/manual-test-examples/type/justified-test.html b/test/manual-test-examples/type/justified-test.html new file mode 100644 index 0000000000..27f4decbc3 --- /dev/null +++ b/test/manual-test-examples/type/justified-test.html @@ -0,0 +1,10 @@ + + + + JUSTIFIED Alignment Test + + + + + +