Skip to content

Commit 73f912b

Browse files
committed
feat(typography): add JUSTIFIED horizontal alignment for bounded text in 2D
- Adds JUSTIFIED constant to src/core/constants.js - Implements word spacing distribution in _renderText for justified text - Last line remains ragged (left-aligned) per typography standards - WebGL falls back to LEFT alignment - Includes manual test demonstrating JUSTIFIED with WORD/CHAR wrap Addresses #7712
1 parent 8d148fb commit 73f912b

File tree

4 files changed

+125
-2
lines changed

4 files changed

+125
-2
lines changed

src/core/constants.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,6 +1072,12 @@ export const CHAR = 'CHAR';
10721072
* @final
10731073
*/
10741074
export const WORD = 'WORD';
1075+
/**
1076+
* @typedef {'JUSTIFIED'} JUSTIFIED
1077+
* @property {JUSTIFIED} JUSTIFIED
1078+
* @final
1079+
*/
1080+
export const JUSTIFIED = 'JUSTIFIED';
10751081

10761082
// TYPOGRAPHY-INTERNAL
10771083
export const _DEFAULT_TEXT_FILL = '#000000';

src/type/textCore.js

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1501,7 +1501,10 @@ function textCore(p5, fn) {
15011501
lines = this._positionLines(x, y, width, height, lines);
15021502

15031503
// render each line at the adjusted position
1504-
lines.forEach(line => this._renderText(line.text, line.x, line.y));
1504+
lines.forEach((line, index) => {
1505+
const isLastLine = index === lines.length - 1;
1506+
this._renderText(line.text, line.x, line.y, Infinity, -Infinity, width, isLastLine);
1507+
});
15051508

15061509
this.textDrawingContext().textBaseline = setBaseline; // restore baseline
15071510
};
@@ -2526,7 +2529,7 @@ function textCore(p5, fn) {
25262529
return this.drawingContext;
25272530
};
25282531

2529-
p5.Renderer2D.prototype._renderText = function (text, x, y, maxY, minY) {
2532+
p5.Renderer2D.prototype._renderText = function (text, x, y, maxY, minY, width, isLastLine) {
25302533
let states = this.states;
25312534
let context = this.textDrawingContext();
25322535

@@ -2536,6 +2539,46 @@ function textCore(p5, fn) {
25362539

25372540
this.push();
25382541

2542+
// Handle JUSTIFIED alignment
2543+
if (states.textAlign === fn.JUSTIFIED && typeof width !== 'undefined' && !isLastLine) {
2544+
const words = text.split(' ');
2545+
if (words.length > 1) {
2546+
const textWidth = this._textWidthSingle(text);
2547+
const spaceWidth = this._textWidthSingle(' ');
2548+
const totalGaps = words.length - 1;
2549+
const extraSpace = width - textWidth;
2550+
const spacePerGap = spaceWidth + (extraSpace / totalGaps);
2551+
2552+
// Render each word with adjusted spacing
2553+
let currentX = x;
2554+
for (let i = 0; i < words.length; i++) {
2555+
const word = words[i];
2556+
2557+
// no stroke unless specified by user
2558+
if (states.strokeColor && states.strokeSet) {
2559+
context.strokeText(word, currentX, y);
2560+
}
2561+
2562+
if (!this._clipping && states.fillColor) {
2563+
// if fill hasn't been set by user, use default text fill
2564+
if (!states.fillSet) {
2565+
this._setFill(DefaultFill);
2566+
}
2567+
context.fillText(word, currentX, y);
2568+
}
2569+
2570+
currentX += this._textWidthSingle(word);
2571+
if (i < words.length - 1) {
2572+
currentX += spacePerGap;
2573+
}
2574+
}
2575+
2576+
this.pop();
2577+
return;
2578+
}
2579+
}
2580+
2581+
// Default rendering for non-JUSTIFIED or last line
25392582
// no stroke unless specified by user
25402583
if (states.strokeColor && states.strokeSet) {
25412584
context.strokeText(text, x, y);
@@ -2580,6 +2623,9 @@ function textCore(p5, fn) {
25802623
case fn.RIGHT:
25812624
adjustedX = x + adjustedW;
25822625
break;
2626+
case fn.JUSTIFIED:
2627+
adjustedX = x; // JUSTIFIED starts from left
2628+
break;
25832629
case textCoreConstants.END:
25842630
throw new Error('textBounds: END not yet supported for textAlign');
25852631
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
function setup() {
2+
createCanvas(800, 600);
3+
background(255);
4+
5+
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.';
6+
let boxWidth = 300;
7+
let boxHeight = 150;
8+
9+
// Test 1: JUSTIFIED with WORD wrap
10+
fill(0);
11+
textSize(16);
12+
textAlign(JUSTIFIED, TOP);
13+
textWrap(WORD);
14+
15+
stroke(200);
16+
noFill();
17+
rect(50, 50, boxWidth, boxHeight);
18+
19+
fill(0);
20+
noStroke();
21+
text('JUSTIFIED + WORD wrap:', 50, 30);
22+
text(sampleText, 50, 50, boxWidth, boxHeight);
23+
24+
// Test 2: JUSTIFIED with CHAR wrap
25+
stroke(200);
26+
noFill();
27+
rect(450, 50, boxWidth, boxHeight);
28+
29+
fill(0);
30+
noStroke();
31+
textWrap(CHAR);
32+
text('JUSTIFIED + CHAR wrap:', 450, 30);
33+
text(sampleText, 450, 50, boxWidth, boxHeight);
34+
35+
// Test 3: LEFT alignment for comparison
36+
stroke(200);
37+
noFill();
38+
rect(50, 250, boxWidth, boxHeight);
39+
40+
fill(0);
41+
noStroke();
42+
textAlign(LEFT, TOP);
43+
textWrap(WORD);
44+
text('LEFT + WORD wrap (comparison):', 50, 230);
45+
text(sampleText, 50, 250, boxWidth, boxHeight);
46+
47+
// Test 4: Show last line is ragged
48+
let multiLineText = 'First line will be justified. Second line will also be justified. But the last line stays ragged.';
49+
50+
stroke(200);
51+
noFill();
52+
rect(450, 250, boxWidth, boxHeight);
53+
54+
fill(0);
55+
noStroke();
56+
textAlign(JUSTIFIED, TOP);
57+
text('JUSTIFIED - last line ragged:', 450, 230);
58+
text(multiLineText, 450, 250, boxWidth, boxHeight);
59+
60+
noLoop();
61+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>JUSTIFIED Alignment Test</title>
5+
<script src="../../../dist/p5.js"></script>
6+
<script src="justified-sketch.js"></script>
7+
</head>
8+
<body>
9+
</body>
10+
</html>

0 commit comments

Comments
 (0)