Skip to content

Commit ef28cbc

Browse files
committed
Get font rendering working on WebGPU
1 parent cb221da commit ef28cbc

File tree

87 files changed

+983
-398
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

87 files changed

+983
-398
lines changed

preview/index.html

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,13 @@
2727
let sh;
2828
let ssh;
2929
let tex;
30+
let font;
3031

3132
p.setup = async function () {
3233
await p.createCanvas(400, 400, p.WEBGPU);
34+
font = await p.loadFont(
35+
'font/PlayfairDisplay.ttf'
36+
);
3337
fbo = p.createFramebuffer();
3438

3539
tex = p.createImage(100, 100);
@@ -72,6 +76,22 @@
7276
};
7377

7478
p.draw = function () {
79+
p.clear();
80+
p.orbitControl();
81+
p.push();
82+
p.textAlign(p.CENTER, p.CENTER);
83+
p.textFont(font);
84+
p.textSize(85)
85+
p.fill('red')
86+
p.noStroke()
87+
p.rect(0, 0, 100, 100);
88+
p.fill(0);
89+
p.push()
90+
p.rotate(p.millis() * 0.001)
91+
p.text('Hello!', 0, 0);
92+
p.pop()
93+
p.pop();
94+
return;
7595
p.orbitControl();
7696
const t = p.millis() * 0.002;
7797
p.background(200);

src/core/p5.Renderer2D.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { RGBHDR } from '../color/creating_reading';
99
import FilterRenderer2D from '../image/filterRenderer2D';
1010
import { Matrix } from '../math/p5.Matrix';
1111
import { PrimitiveToPath2DConverter } from '../shape/custom_shapes';
12+
import { DefaultFill, textCoreConstants } from '../type/textCore';
1213

1314

1415
const styleEmpty = 'rgba(0,0,0,0)';
@@ -1054,6 +1055,108 @@ class Renderer2D extends Renderer {
10541055

10551056
super.pop(style);
10561057
}
1058+
1059+
// Text support methods
1060+
textCanvas() {
1061+
return this.canvas;
1062+
}
1063+
1064+
textDrawingContext() {
1065+
return this.drawingContext;
1066+
}
1067+
1068+
_renderText(text, x, y, maxY, minY) {
1069+
let states = this.states;
1070+
let context = this.textDrawingContext();
1071+
1072+
if (y < minY || y >= maxY) {
1073+
return; // don't render lines beyond minY/maxY
1074+
}
1075+
1076+
this.push();
1077+
1078+
// no stroke unless specified by user
1079+
if (states.strokeColor && states.strokeSet) {
1080+
context.strokeText(text, x, y);
1081+
}
1082+
1083+
if (!this._clipping && states.fillColor) {
1084+
1085+
// if fill hasn't been set by user, use default text fill
1086+
if (!states.fillSet) {
1087+
this._setFill(DefaultFill);
1088+
}
1089+
context.fillText(text, x, y);
1090+
}
1091+
1092+
this.pop();
1093+
}
1094+
1095+
/*
1096+
Position the lines of text based on their textAlign/textBaseline properties
1097+
*/
1098+
_positionLines(x, y, width, height, lines) {
1099+
let { textLeading, textAlign } = this.states;
1100+
let adjustedX, lineData = new Array(lines.length);
1101+
let adjustedW = typeof width === 'undefined' ? 0 : width;
1102+
let adjustedH = typeof height === 'undefined' ? 0 : height;
1103+
1104+
for (let i = 0; i < lines.length; i++) {
1105+
switch (textAlign) {
1106+
case textCoreConstants.START:
1107+
throw new Error('textBounds: START not yet supported for textAlign'); // default to LEFT
1108+
case constants.LEFT:
1109+
adjustedX = x;
1110+
break;
1111+
case constants.CENTER:
1112+
adjustedX = x + adjustedW / 2;
1113+
break;
1114+
case constants.RIGHT:
1115+
adjustedX = x + adjustedW;
1116+
break;
1117+
case textCoreConstants.END:
1118+
throw new Error('textBounds: END not yet supported for textAlign');
1119+
}
1120+
lineData[i] = { text: lines[i], x: adjustedX, y: y + i * textLeading };
1121+
}
1122+
1123+
return this._yAlignOffset(lineData, adjustedH);
1124+
}
1125+
1126+
/*
1127+
Get the y-offset for text given the height, leading, line-count and textBaseline property
1128+
*/
1129+
_yAlignOffset(dataArr, height) {
1130+
if (typeof height === 'undefined') {
1131+
throw Error('_yAlignOffset: height is required');
1132+
}
1133+
1134+
let { textLeading, textBaseline } = this.states;
1135+
let yOff = 0, numLines = dataArr.length;
1136+
let ydiff = height - (textLeading * (numLines - 1));
1137+
1138+
switch (textBaseline) { // drawingContext ?
1139+
case constants.TOP:
1140+
break; // ??
1141+
case constants.BASELINE:
1142+
break;
1143+
case textCoreConstants._CTX_MIDDLE:
1144+
yOff = ydiff / 2;
1145+
break;
1146+
case constants.BOTTOM:
1147+
yOff = ydiff;
1148+
break;
1149+
case textCoreConstants.IDEOGRAPHIC:
1150+
console.warn('textBounds: IDEOGRAPHIC not yet supported for textBaseline'); // FES?
1151+
break;
1152+
case textCoreConstants.HANGING:
1153+
console.warn('textBounds: HANGING not yet supported for textBaseline'); // FES?
1154+
break;
1155+
}
1156+
1157+
dataArr.forEach(ele => ele.y += yOff);
1158+
return dataArr;
1159+
}
10571160
}
10581161

10591162
function renderer2D(p5, fn){

src/core/p5.Renderer3D.js

Lines changed: 97 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { Color } from "../color/p5.Color";
1313
import { Element } from "../dom/p5.Element";
1414
import { Framebuffer } from "../webgl/p5.Framebuffer";
1515
import { DataArray } from "../webgl/p5.DataArray";
16+
import { textCoreConstants } from "../type/textCore";
1617
import { RenderBuffer } from "../webgl/p5.RenderBuffer";
1718
import { Image } from "../image/p5.Image";
1819
import { Texture } from "../webgl/p5.Texture";
@@ -1643,17 +1644,6 @@ export class Renderer3D extends Renderer {
16431644
}
16441645
}
16451646

1646-
_setPointUniforms(pointShader) {
1647-
// set the uniform values
1648-
pointShader.setUniform("uMaterialColor", this.states.curStrokeColor);
1649-
// @todo is there an instance where this isn't stroke weight?
1650-
// should be they be same var?
1651-
pointShader.setUniform(
1652-
"uPointSize",
1653-
this.states.strokeWeight * this._pixelDensity
1654-
);
1655-
}
1656-
16571647
/**
16581648
* @private
16591649
* Note: DO NOT CALL THIS while in the middle of binding another texture,
@@ -1731,4 +1721,100 @@ export class Renderer3D extends Renderer {
17311721
_vToNArray(arr) {
17321722
return arr.flatMap((item) => [item.x, item.y, item.z]);
17331723
}
1724+
1725+
///////////////////////////////
1726+
//// TEXT SUPPORT METHODS
1727+
//////////////////////////////
1728+
1729+
textCanvas() {
1730+
if (!this._textCanvas) {
1731+
this._textCanvas = document.createElement('canvas');
1732+
this._textCanvas.width = 1;
1733+
this._textCanvas.height = 1;
1734+
this._textCanvas.style.display = 'none';
1735+
// Has to be added to the DOM for measureText to work properly!
1736+
this.canvas.parentElement.insertBefore(this._textCanvas, this.canvas);
1737+
}
1738+
return this._textCanvas;
1739+
}
1740+
1741+
textDrawingContext() {
1742+
if (!this._textDrawingContext) {
1743+
const textCanvas = this.textCanvas();
1744+
this._textDrawingContext = textCanvas.getContext('2d');
1745+
}
1746+
return this._textDrawingContext;
1747+
}
1748+
1749+
_positionLines(x, y, width, height, lines) {
1750+
let { textLeading, textAlign } = this.states;
1751+
const widths = lines.map(line => this._fontWidthSingle(line));
1752+
let adjustedX, lineData = new Array(lines.length);
1753+
let adjustedW = typeof width === 'undefined' ? Math.max(0, ...widths) : width;
1754+
let adjustedH = typeof height === 'undefined' ? 0 : height;
1755+
1756+
for (let i = 0; i < lines.length; i++) {
1757+
switch (textAlign) {
1758+
case textCoreConstants.START:
1759+
throw new Error('textBounds: START not yet supported for textAlign'); // default to LEFT
1760+
case constants.LEFT:
1761+
adjustedX = x;
1762+
break;
1763+
case constants.CENTER:
1764+
adjustedX = x +
1765+
(adjustedW - widths[i]) / 2 -
1766+
adjustedW / 2 +
1767+
(width || 0) / 2;
1768+
break;
1769+
case constants.RIGHT:
1770+
adjustedX = x + adjustedW - widths[i] - adjustedW + (width || 0);
1771+
break;
1772+
case textCoreConstants.END:
1773+
throw new Error('textBounds: END not yet supported for textAlign');
1774+
default:
1775+
adjustedX = x;
1776+
break;
1777+
}
1778+
lineData[i] = { text: lines[i], x: adjustedX, y: y + i * textLeading };
1779+
}
1780+
1781+
return this._yAlignOffset(lineData, adjustedH);
1782+
}
1783+
1784+
_yAlignOffset(dataArr, height) {
1785+
if (typeof height === 'undefined') {
1786+
throw Error('_yAlignOffset: height is required');
1787+
}
1788+
1789+
let { textLeading, textBaseline, textSize, textFont } = this.states;
1790+
let yOff = 0, numLines = dataArr.length;
1791+
let totalHeight = textSize * numLines +
1792+
((textLeading - textSize) * (numLines - 1));
1793+
switch (textBaseline) { // drawingContext ?
1794+
case constants.TOP:
1795+
yOff = textSize;
1796+
break;
1797+
case constants.BASELINE:
1798+
break;
1799+
case textCoreConstants._CTX_MIDDLE:
1800+
yOff = -totalHeight / 2 + textSize + (height || 0) / 2;
1801+
break;
1802+
case constants.BOTTOM:
1803+
yOff = -(totalHeight - textSize) + (height || 0);
1804+
break;
1805+
default:
1806+
console.warn(`${textBaseline} is not supported in WebGL mode.`); // FES?
1807+
break;
1808+
}
1809+
yOff += this.states.textFont.font?._verticalAlign(textSize) || 0;
1810+
dataArr.forEach(ele => ele.y += yOff);
1811+
return dataArr;
1812+
}
1813+
1814+
remove() {
1815+
if (this._textCanvas) {
1816+
this._textCanvas.parentElement.removeChild(this._textCanvas);
1817+
}
1818+
super.remove();
1819+
}
17341820
}

src/type/p5.Font.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,6 +1064,9 @@ async function create(pInst, name, path, descriptors, rawFont) {
10641064
// ensure the font is ready to be rendered
10651065
await document.fonts.ready;
10661066

1067+
// Await loading of the font via CSS in case it also loads other resources
1068+
await document.fonts.load(`1em "${name}"`);
1069+
10671070
// return a new p5.Font
10681071
return new Font(pInst, face, name, path, rawFont);
10691072
}

0 commit comments

Comments
 (0)