├── README.md ├── examples ├── googleFonts-2axis.js ├── graphics-fontOnCircle.js └── measurements.js ├── index.html ├── p5.variableFont.js ├── src ├── BricolageGrotesque-VariableFont.ttf └── example.mp4 └── style.css /README.md: -------------------------------------------------------------------------------- 1 | # p5.variableFont 2 |  3 | Font credits: Oh No - Obviously 4 | 5 | ## Bringing variable fonts to p5.js 🎉 6 | 7 | I've modified several p5.js functions to support variable fonts and Google fonts (and by extension, Google variable fonts!). Everything works, but there are some 'duct tape' fixes in the code to maintain the original p5 workflow. I'll integrate these better if there's enough interest. For now, please read the documentation :) 8 | 9 | This is built around p5.js `v1.7.0` but should work with previous versions. It can be used on either the main `canvas` or a `p5.Graphics`. There's a known bug related to animating some axes within a `p5.Graphics`. All the standard methods like `fill()`, `stroke()`, `textWidth()`, `textLeading()`, and so on are supported. It even works with native `drawingContext` functions like `ctx.wordSpacing`. 10 | 11 | Although this build depends on p5.js, you can adapt it for a vanilla webCanvas! 12 | 13 | I'd love to see anything cool you create with it. Let me know if you encounter any issues. Have fun! 14 | 15 | ## Important ! 16 | 17 | There are two primary methods for loading and using fonts in p5.js. One involves using `loadFont()`, and the other is by defining a font either through CSS with a `@font-face` tag or importing it via an API like the Google Fonts API. Both methods have the same end result when using `text()` in the 2D context. However, differences emerge when you delve into font data functions like `font.textToPoints()` or `font.getBounds()`, or when using the WEBGL context. 18 | 19 | For variable fonts in p5.js, you'll need to use the CSS method. Instead of `textFont(loadedFont)`, you'd call `textFont('YourFontName')` where the name is either the `font-family` from the [CSS specification](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face), the name of the Google Font (e.g., `Playfair Display`), or I've added a new argument to p5's `loadFont()` to define a CSS name as a fourth argument for user-loaded fonts. 20 | 21 | ## Also Important! 22 | 23 | p5.js cannot render a font variation that's unavailable, out of range, misnamed, or in the case of an API-requested font, unloaded. Ensure you load the correct variable font, with matching axis names, and values within the proper range. Most of the time, retailers provide these names and min-max values. However, you can also use tools like the [Dinamo Font Gauntlet](https://fontgauntlet.com/) or the built-in `font.getVariations()` for loaded p5.Fonts. 24 | 25 | ## Kinda Important too! 26 | 27 | ### Limitations & Bugs 28 | 29 | #### 🐛 Bug: Animating a Single Axis on a Multi-Axis Font in `p5.Graphics` 30 | Trying to animate a single axis in a `p5.Graphics` without adjusting the `wght` or `textWeight()` might result in no animation. I'm working on this issue. 31 | 32 | #### Workaround 33 | Ensure you animate the `wght` axis if it exists. You can create a minimal variation, for instance, oscillating between 400 and 401. 34 | 35 | #### 🚫 Limitation: No Variable Setting in `opentype.js` 36 | There's currently no way to request a specific character with unique variable axis values from `opentype.js`. This limitation impacts functions like `font.textToPoints()` and displaying a font in WEBGL since both require `opentype.js` for parsing. As a result, the font loads with minimal axis values, preventing any animation. 37 | 38 | #### Workaround 39 | If you need your text drawn using points, one solution involves uploading both opentype fonts with their minimal-maximal axis values. You might need apps like Glyphs or similar for this. Once you've obtained the points for minimal and maximal values, you can interpolate any font weight by positioning a point between the min/max values. 40 | 41 | You can also extract a 'single weight' using the [Dinamo font gauntlet](https://fontgauntlet.com/). 42 | 43 | #### 🚫 Limitation: No Support for WebGL Context (Yet) 44 | p5.js uses `opentype.js` to parse font files for rendering in 3D space. Refer to the [following paper](https://jcgt.org/published/0006/02/02/paper.pdf) and the [p5.js source code](https://github.com/processing/p5.js/blob/main/src/webgl/text.js) for details. 45 | 46 | #### Workaround 47 | A common solution is to render text on an image using p5.js's `createGraphics()` or a WebGL texture (more details [here](https://webglfundamentals.org/webgl/lessons/webgl-text-texture.html)). One tip is to use the 2D canvas font metrics to obtain the optimal texture size for containing text. More information on this technique can be found [here](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_text). 48 | 49 | #### 🔎 Nice to have: accessing and caching an API requested font file, axis and measurement 50 | 51 | Getting access to the file that is requested i.e. from 'font.gstatic.com' by the 'fonts.googleapis.com' to pass it through `opentype.js` could help to harmonize the process and make it more coherent with the pipeline already being used by p5.js. 52 | 53 | #### Workaround 54 | Still looking 👀 55 | 56 | --- 57 | 58 | ## Methods 59 | 60 | ### textFont() 61 | 62 | You can define the size using a second parameter 63 | 64 | ```js 65 | function textFont(font:string, textsize:number) 66 | ``` 67 | 68 | The main and most common axis is the `wght`, so with a single number, you will adjust your font weight. 69 | 70 | ```js 71 | function textFont(font:string, textsize:number, variation:number) 72 | ``` 73 | 74 | Using an object as the third argument tell p5.js to display a font with the given variations (can be animated !) 75 | 76 | ```js 77 | function textFont(font:string, textsize:number, variation:{key: value:number, ...}}) 78 | ``` 79 | 80 | --- 81 | 82 | ### textWeight() 83 | 84 | Same idea as above but only for the text weight (make sure that the weights exists and/or are loaded) 85 | 86 | ```js 87 | function textWeight(weight:number) 88 | ``` 89 | 90 | --- 91 | 92 | ### textVariations() 93 | 94 | Same idea as above but only for the text weight (make sure that the weight exists and/or are loaded) 95 | 96 | ```js 97 | function textVariations(weight:number) 98 | ``` 99 | 100 | Same idea as above but for the text variations (make sure that the weights exists and/or are loaded) 101 | 102 | ```js 103 | function textVariations(variation:{key: value:number, ...}) 104 | ``` 105 | 106 | --- 107 | 108 | ### measureText() 109 | 110 | Get the bounding box for the a given string. Need to be called after either `textWeight()`, `textVariations()` or `textFont()` with variable parameters to be taken into account by the drawing context. 111 | 112 | ! Important : Since we are tapping directly in the font measurement, you will have to vertically adjust the bounding box to match the text precisely on the screen. This operation is usually done by the browser but we have to do it manually here. The measurement provide a `offset` value that can be used for that purpose. 113 | 114 | ```js 115 | function measureText(text : string) 116 | return { 117 | width : number, 118 | height : number, 119 | offset : number 120 | } 121 | ``` 122 | 123 | --- 124 | 125 | ### textStretch() 126 | 127 | ```js 128 | function textStretch(stretch:number) 129 | ``` 130 | Currently looking into that 👀 131 | 132 | --- 133 | ### loadFont() 134 | 135 | ```js 136 | function loadFont(font:URL, onSuccess:function, onError:function, CSSname:string) 137 | ``` 138 | 139 | #### Important 140 | 141 | This function needs opentype.js to be loaded externally, p5 relies on it to get the font object needed to load a p5Font. Since i'm exposing `opentype`, please add this to your `
` : 142 | `` 143 | 144 | The function is basically the same but take a fourth argument to define the font name in CSS so you can use it `textFont('CSSname')` 145 | I didn't figured out a way to add this without changing the p5.js default behavior. It's a bit verbose due to the callbacks, you can use it like this : 146 | 147 | ```js 148 | function preload() { 149 | font = loadFont( 150 | "yourfont.ttf", 151 | () => {}, 152 | () => {}, 153 | "yourfontname" 154 | ); 155 | } 156 | ``` 157 | 158 | ### font.getVariations() 159 | Return the variables axis with their respective min/max and default value. 160 | Setting the 'log' to `true` will display the result in the console, usefull if needed only for reference. 161 | 162 | ```js 163 | let axis = font.getVariations(((log: boolean) = false)); 164 | return { key: axis, values: { default: number, min: number, max: number } }; 165 | ``` 166 | 167 | 168 | 169 | ### font.getStyles() 170 | Return the style and their corresponding values on the variables axis. 171 | Setting the 'log' to `true` will display the result in the console. 172 | 173 | ```js 174 | let styles = font.getStyles(log:boolean=false) 175 | return {key:style,values:{ { key: axis, value:number},...} 176 | ``` 177 | 178 | 179 | 180 | The output is fitted to be used in the `textFont()` 181 | 182 | ```js 183 | let styles = font.getStyles(); 184 | textFont("your-font", size, styles[BOLD]); 185 | ``` 186 | 187 | Those two function can be used either in the `loadFont()` : 188 | 189 | ```js 190 | let font; 191 | function preload() { 192 | font = loadFont( 193 | "yourfont.ttf", 194 | (font) => { 195 | axis = font.getVariations(true); 196 | styles = font.getStyles(true); 197 | }, 198 | () => {}, 199 | "yourfontname" 200 | ); 201 | } 202 | ``` 203 | 204 | or in anywhere else using the font object : 205 | 206 | ```js 207 | let font; 208 | function preload() { 209 | font = loadFont( 210 | "yourfont.ttf", 211 | () => {}, 212 | () => {}, 213 | "yourfontname" 214 | ); 215 | } 216 | function setup() { 217 | axis = font.getVariations(); 218 | stles = font.getStyles(); 219 | } 220 | ``` 221 | 222 | --- 223 | 224 | ### loadGoogleFont() 225 | 226 | Small helper function to load a Google Font. It will build a `` element and inject it in the ``. 227 | Make sure to check the exact values and font names on: https://fonts.google.com/. 228 | This function take a `name` as an input and will build an URL according to the Google Fonts API URLs structure. 229 | 230 | #### Important : Preloading a Google Font 231 | 232 | If you need or want to preload a Google Font (which i strongly suggest) with all its variable axis, you will need to adjust your URL. 233 | Here's a guide on how to do it : https://www.launch2success.com/guide/getting-google-font-variable-files/ 234 | 235 | In short, you will need to tell Google Font to load all the variations within a certain range. Again, make sure to check the min/max values and names. Unless you're on a super tight ressources management or you know exactly which weights/variations you need, get the widest range of values available. As mentioned before, if you try to display a variation at a value not loaded as Google Font, it will display a default font. 236 | 237 | It should look like something like this : 238 | 239 | ```html 240 | 241 | 242 | 246 | ``` 247 | 248 | If you uploaded a Google Font using the preloading method, you won't need to use the following functions. Those works in real-time so you can use these to dynamically add a Google Font to your code. 249 | 250 | Load a Google Font to be used with `textFont('name')`. 251 | 252 | ```js 253 | function loadGoogleFont(name:string) 254 | ``` 255 | 256 | Load a Google Font at a given weight. 257 | 258 | ```js 259 | function loadGoogleFont(name:string, variation:number) 260 | ``` 261 | 262 | Load a Google Font and its weights between the min-max 263 | 264 | ```js 265 | function loadGoogleFont(name:string, variation:[min:number,max:number]]) 266 | ``` 267 | 268 | Load a Google Font with a given variation axis at a given value 269 | 270 | ```js 271 | function loadGoogleFont(name:string, variation:{key: value:number, ...}) 272 | ``` 273 | 274 | Load a Google Font and its variations ranging within a min-max 275 | 276 | ```js 277 | function loadGoogleFont(name:string, variation:{key: value:[min:number,max:number], ...}) 278 | ``` 279 | 280 | # Under the hood 281 | 282 | I will try to explain a little bit how things are happening from what i understood so maybe some others interested people might jump in and participate in the effort to get a more diverse type support on p5.js and webcanvas. 283 | 284 | ## Variable fonts in WebCanvas 285 | 286 | ### How p5.js handle fonts 287 | 288 | #### Native canvas 289 | 290 | #### opentype.js 291 | 292 | ### Every font on the web is either displayed using CSS or rasterized bezier curves 293 | 294 | ## Webcanvas use a CSS-like approach to 'style' the text before displaying it 295 | 296 | ### We can use CSS to dynamically change the style of a character by directly setting the style to the canvas element 297 | 298 | ### Exploring the relationship between CSS and WebCanvas 299 | 300 | 301 | -------------------------------------------------------------------------------- /examples/googleFonts-2axis.js: -------------------------------------------------------------------------------- 1 | function preload() { 2 | 3 | // both will do the same thing 4 | 5 | // loadFont( 6 | // "../src/BricolageGrotesque-VariableFont.ttf", 7 | // (font) => { 8 | // const axis = font.getVariations(true); 9 | // const styles = font.getStyles(true); 10 | // }, 11 | // () => {}, 12 | // "Bricolage Grotesque" 13 | // ); 14 | 15 | 16 | loadGoogleFont("Bricolage Grotesque", { 17 | wght: [200, 800], 18 | wdth: [75, 100], 19 | }); 20 | } 21 | 22 | function setup() { 23 | canvas = createCanvas(1200, 500); 24 | textAlign(CENTER, CENTER); 25 | rectMode(CENTER); 26 | } 27 | 28 | function draw() { 29 | background(220); 30 | 31 | background(220); 32 | 33 | const n = 75 + (sin(frameCount * 0.05) * 0.5 + 0.5) * 25; 34 | const m = 200 + (cos(frameCount * 0.05) * 0.5 + 0.5) * 600; 35 | push(); 36 | textFont("Bricolage Grotesque", 60, { wdth: n, wght: m }); 37 | text("HEY THERE!", width / 2, height / 2); 38 | pop(); 39 | } 40 | 41 | function quadratic(p) { 42 | var m = p - 1, 43 | t = p * 2; 44 | if (t < 1) return p * t; 45 | return 1 - m * m * 2; 46 | } 47 | -------------------------------------------------------------------------------- /examples/graphics-fontOnCircle.js: -------------------------------------------------------------------------------- 1 | function preload() { 2 | loadGoogleFont("Bricolage Grotesque", { 3 | wght: [200, 800], 4 | wdth: [75, 100], 5 | }); 6 | } 7 | 8 | function setup() { 9 | createCanvas(800, 800); 10 | textAlign(CENTER, CENTER); 11 | 12 | // done using a p5.Graphics for the example 13 | pg = createGraphics(width, height); 14 | pg.background("blue"); 15 | pg.textAlign(CENTER, CENTER); 16 | pg.fill("#fff"); 17 | } 18 | 19 | function draw() { 20 | clear(); 21 | background("#0060E5"); 22 | push(); 23 | const f = 6 * 60; 24 | const c = "a"; 25 | const t = new Array(13).fill(c).reduce((acc, curr) => acc + curr, '') 26 | const p = (frameCount % f) / f; 27 | 28 | const txtSize = 200; 29 | const letters = t 30 | .split() 31 | .map((word) => word.split("")) 32 | .flat(); 33 | pg.reset(); 34 | pg.clear(); 35 | pg.background("blue"); 36 | let totalWidth = 0; 37 | const datas = letters.map((letter, id, arr) => { 38 | const progress = quadratic( 39 | sin(p * TWO_PI + (id / arr.length) * TWO_PI * 2) * 0.5 + 0.5 40 | ); 41 | const wdth = map(progress, 0, 1, 75, 100); 42 | const wght = map(progress, 0, 1, 200, 800); 43 | 44 | pg.push(); 45 | pg.textFont("Bricolage Grotesque", txtSize, { 46 | wdth: wdth, 47 | wght: wght, 48 | }); 49 | const letterWidth = pg.textWidth(letter); 50 | totalWidth += letterWidth; 51 | pg.pop(); 52 | return { 53 | character: letter, 54 | wdth: wdth, 55 | wght: wght, 56 | letterWidth: letterWidth, 57 | }; 58 | }); 59 | 60 | let arcLength = 0; 61 | const radius = (totalWidth / TWO_PI) * 1.1; 62 | datas.forEach((char, i, arr) => { 63 | const { character, letterWidth, wdth, wght } = char; 64 | const lettersRadius = radius; 65 | arcLength += letterWidth / 2; 66 | const angle = (arcLength / totalWidth) * TWO_PI; 67 | const x = pg.width / 2 + Math.cos(angle) * lettersRadius; 68 | const y = pg.height / 2 + Math.sin(angle) * lettersRadius; 69 | pg.push(); 70 | pg.textFont("Bricolage Grotesque", txtSize, { wdth: wdth, wght: wght }); 71 | // rotate(a) 72 | pg.translate(x, y); 73 | pg.rotate(angle + PI / 2); 74 | pg.text(character, 0, 0); 75 | pg.pop(); 76 | arcLength += letterWidth / 2; 77 | }); 78 | 79 | image(pg, 0, 0); 80 | } 81 | 82 | function quadratic(p) { 83 | var m = p - 1, 84 | t = p * 2; 85 | if (t < 1) return p * t; 86 | return 1 - m * m * 2; 87 | } 88 | -------------------------------------------------------------------------------- /examples/measurements.js: -------------------------------------------------------------------------------- 1 | function preload() { 2 | loadGoogleFont("Bricolage Grotesque", { 3 | wght: [200, 800], 4 | wdth: [75, 100], 5 | }); 6 | } 7 | 8 | function setup() { 9 | canvas = createCanvas(1080, 1080); 10 | // frameRate(8) 11 | textAlign(CENTER, CENTER); 12 | rectMode(CENTER); 13 | } 14 | 15 | function draw() { 16 | background(0); 17 | 18 | const n = 75 + quadratic(sin(frameCount * 0.03) * 0.5 + 0.5) * 25; 19 | const m = 200 + quadratic(sin(frameCount * 0.03) * 0.5 + 0.5) * 600; 20 | const s = 180 - quadratic(sin(frameCount * 0.03) * 0.5 + 0.5) * 125; 21 | 22 | 23 | 24 | 25 | push(); 26 | push(); 27 | textFont("Bricolage Grotesque", s, { wdth: n, wght: m }); 28 | let txt = "MEASUREMENTS"; 29 | let f = measureText(txt); 30 | fill("ivory"); 31 | text(txt, width / 2, height / 2); 32 | pop(); 33 | push(); 34 | noFill(); 35 | stroke("blue"); 36 | rect(width / 2, height / 2 + f.offset, f.width, f.height); 37 | push(); 38 | textFont("Courier"); 39 | fill("blue"); 40 | line(width / 2 - f.width / 2, 0, width / 2 - f.width / 2, height); 41 | line(width / 2 + f.width / 2, 0, width / 2 + f.width / 2, height); 42 | line( 43 | 0, 44 | height / 2 - f.height / 2 + f.offset, 45 | width, 46 | height / 2 - f.height / 2 + f.offset 47 | ); 48 | line( 49 | 0, 50 | height / 2 + f.height / 2 + f.offset, 51 | width, 52 | height / 2 + f.height / 2 + f.offset 53 | ); 54 | push(); 55 | translate(width / 2 + f.width / 2 - 60, height / 2 - (f.height / 2) * 1.4); 56 | noStroke(); 57 | rect(0, 0, 120, 20); 58 | fill("white"); 59 | text("tight bounds", 0, 0); 60 | pop(); 61 | push(); 62 | translate(width / 2 - f.width / 2 + 60, height / 2 + (f.height / 2) * 1.05); 63 | noStroke(); 64 | rect(0, 0, 120, 20); 65 | fill("white"); 66 | text(`${f.width.toFixed(0)} x ${f.height.toFixed(0)}`, 0, 0); 67 | pop(); 68 | pop(); 69 | pop(); 70 | 71 | 72 | } 73 | 74 | function quadratic(p) { 75 | var m = p - 1, 76 | t = p * 2; 77 | if (t < 1) return p * t; 78 | return 1 - m * m * 2; 79 | } 80 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /p5.variableFont.js: -------------------------------------------------------------------------------- 1 | p5.Font.prototype.getVariations = function (logTables = false) { 2 | const axes = this.font.tables.fvar.axes; 3 | if (axes !== undefined || axes.length === 0) { 4 | let data = {}; 5 | for (let axe of axes) { 6 | data[axe.tag] = { 7 | default: axe.defaultValue, 8 | min: axe.minValue, 9 | max: axe.maxValue, 10 | }; 11 | } 12 | if (logTables) { 13 | console.log(data); 14 | } 15 | return data; 16 | } else { 17 | console.log("No valid font tables found, make sure to load a variable font"); 18 | } 19 | }; 20 | 21 | p5.Font.prototype.getStyles = function (logTables = false) { 22 | const styles = this.font.tables.fvar.instances; 23 | if (styles !== undefined && styles.length !== 0) { 24 | let data = {}; 25 | for (let style of styles) { 26 | const { name, coordinates } = style; 27 | data[name.en] = coordinates; 28 | } 29 | if (logTables) { 30 | console.log(data); 31 | } 32 | return data; 33 | } else { 34 | console.log("No valid font tables found, make sure to load a variable font"); 35 | } 36 | }; 37 | 38 | p5.prototype.textWeight = function (theWeight) { 39 | return this._renderer._setProperty("_textWeight", theWeight); 40 | }; 41 | 42 | p5.prototype.measureText = function (text) { 43 | const ctx = this._renderer.drawingContext; 44 | const bounds = ctx.measureText(text); 45 | const vertical = { 46 | ascent: bounds.actualBoundingBoxAscent, 47 | descent: bounds.actualBoundingBoxDescent, 48 | }; 49 | const horizontal = { 50 | left: bounds.actualBoundingBoxLeft, 51 | right: bounds.actualBoundingBoxRight, 52 | }; 53 | const height = bounds.actualBoundingBoxAscent + bounds.actualBoundingBoxDescent; 54 | const _width = bounds.actualBoundingBoxLeft + bounds.actualBoundingBoxRight; 55 | const verticalOffset = (vertical.ascent - vertical.descent) * 0.5; 56 | const horizontalOffset = (horizontal.left - horizontal.right) * 12 57 | 58 | return { 59 | offset: -verticalOffset, 60 | width: bounds.width, 61 | height: height, 62 | }; 63 | }; 64 | 65 | p5.prototype.textVariations = function (theVariations) { 66 | if (theVariations) { 67 | if (typeof theVariations === "number") { 68 | this._renderer._setProperty("_textWeight", theVariations); 69 | } else { 70 | let fontVariationsSettings = ""; 71 | for (const variation in theVariations) { 72 | const name = variation; 73 | const value = theVariations[name]; 74 | fontVariationsSettings += `'${variation}'${value},`; 75 | } 76 | let formatedfontVariationsSettings = fontVariationsSettings.slice(0, -1); 77 | this._renderer._setProperty( 78 | "_fontVariations", 79 | formatedfontVariationsSettings 80 | ); 81 | } 82 | } 83 | }; 84 | 85 | p5.prototype.loadFont = function (path, onSuccess, onError, CSSname = "") { 86 | //p5._validateParameters('loadFont', arguments); 87 | const p5Font = new p5.Font(this); 88 | let fontName; 89 | 90 | const self = this; 91 | /* to be used in the p5 editors or modules to ensure to import opentype.js if not there already*/ 92 | if (typeof opentype === "undefined") { 93 | console.log( 94 | "It does seems that you are trying to load a font without opentype.js, add : '' to you html " 95 | ); 96 | return; 97 | } 98 | opentype.load(path, (err, font) => { 99 | if (err) { 100 | p5._friendlyFileLoadError(4, path); 101 | if (typeof onError !== "undefined") { 102 | return onError(err); 103 | } 104 | console.error(err, path); 105 | return; 106 | } 107 | p5Font.font = font; 108 | 109 | if (typeof onSuccess !== "undefined") { 110 | onSuccess(p5Font); 111 | } 112 | self._decrementPreload(); 113 | 114 | // check that we have an acceptable font type 115 | const validFontTypes = ["ttf", "otf", "woff", "woff2"]; 116 | 117 | const fileNoPath = path.split("\\").pop().split("/").pop(); 118 | 119 | const lastDotIdx = fileNoPath.lastIndexOf("."); 120 | let fontFamily; 121 | let newStyle; 122 | const fileExt = lastDotIdx < 1 ? null : fileNoPath.slice(lastDotIdx + 1); 123 | 124 | // if so, add it to the DOM (name-only) for use with DOM module 125 | if (validFontTypes.includes(fileExt)) { 126 | fontFamily = CSSname 127 | ? CSSname 128 | : fileNoPath.slice(0, lastDotIdx !== -1 ? lastDotIdx : 0); 129 | newStyle = document.createElement("style"); 130 | newStyle.appendChild( 131 | document.createTextNode( 132 | `\n@font-face {\nfont-family: ${fontFamily};\nsrc: url(${path});\n}\n` 133 | ) 134 | ); 135 | document.head.appendChild(newStyle); 136 | } 137 | }); 138 | return p5Font; 139 | }; 140 | 141 | p5.prototype.textFont = function (theFont, theSize, theVariations) { 142 | // p5._validateParameters('textFont', arguments); 143 | if (arguments.length) { 144 | if (!theFont) { 145 | throw new Error("null font passed to textFont"); 146 | } 147 | this._renderer._applyTextProperties = function () { 148 | let font; 149 | const p = this._pInst; 150 | this._setProperty("_textAscent", null); 151 | this._setProperty("_textDescent", null); 152 | font = this._textFont; 153 | if (this._isOpenType()) { 154 | font = this._textFont.font.familyName; 155 | this._setProperty("_textStyle", this._textFont.font.styleName); 156 | } 157 | this.canvas.style.fontVariationSettings = this._fontVariations; 158 | this.drawingContext.font = `${this._textWeight || "normal"} ${ 159 | this._textStyle || "normal" 160 | } ${this._textSize || 12}px ${font || "sans-serif"}`; 161 | 162 | this.drawingContext.textAlign = this._textAlign; 163 | if (this._textBaseline === CENTER) { 164 | this.drawingContext.textBaseline = _CTX_MIDDLE; 165 | } else { 166 | this.drawingContext.textBaseline = this._textBaseline; 167 | } 168 | return p; 169 | }; 170 | 171 | if (theVariations) { 172 | if (typeof theVariations === "number") { 173 | this._renderer._setProperty("_textWeight", theVariations); 174 | } else { 175 | let fontVariationsSettings = ""; 176 | for (const variation in theVariations) { 177 | const name = variation; 178 | const value = theVariations[name]; 179 | fontVariationsSettings += `'${variation}'${value},`; 180 | if (name === "wght") { 181 | this._renderer._setProperty("_textWeight", value); 182 | } 183 | } 184 | let formatedfontVariationsSettings = fontVariationsSettings.slice(0, -1); 185 | this._renderer._setProperty( 186 | "_fontVariations", 187 | formatedfontVariationsSettings 188 | ); 189 | } 190 | }else{ 191 | this._renderer._setProperty( 192 | "_fontVariations", 193 | "" 194 | ); 195 | this._renderer._setProperty( 196 | "_textWeight", 197 | "" 198 | ); 199 | } 200 | 201 | this._renderer._setProperty("_textFont", theFont); 202 | if (theSize) { 203 | this._renderer._setProperty("_textSize", theSize); 204 | if (!this._renderer._leadingSet) { 205 | // only use a default value if not previously set (#5181) 206 | this._renderer._setProperty("_textLeading", theSize * _DEFAULT_LEADMULT); 207 | } 208 | } 209 | 210 | return this._renderer._applyTextProperties(); 211 | } 212 | 213 | return this._renderer._textFont; 214 | }; 215 | 216 | p5.prototype.loadGoogleFont = function (name, theVariations) { 217 | const collapsedName = name.split(" ").join("+"); 218 | let variations = ""; 219 | if (theVariations) { 220 | // if a single number is passed, we can assume that we want to load the weight 221 | if (typeof theVariations === "number") { 222 | variations = `:wght@${theVariations}`; 223 | } 224 | // if an array is passed, we want to load all the weights between the two values 225 | else if (Array.isArray(theVariations)) { 226 | variations += `:wght@${theVariations[0]}..${theVariations[1]}`; 227 | } else { 228 | const sortedVariations = Object.keys(theVariations) 229 | .sort() 230 | .reduce((result, key) => { 231 | result[key] = theVariations[key]; 232 | return result; 233 | }, {}); 234 | 235 | // if an obect is passed, we want to load all the axis 236 | let axis = ":"; 237 | let values = ""; 238 | for (const variation in sortedVariations) { 239 | axis += variation + ","; 240 | if (Array.isArray(sortedVariations[variation])) { 241 | values += `${sortedVariations[variation][0]}..${sortedVariations[variation][1]},`; 242 | } else { 243 | values += sortedVariations[variation] + ","; 244 | } 245 | } 246 | axis = axis.slice(0, -1); 247 | values = values.slice(0, -1); 248 | variations += axis.concat("@", values); 249 | } 250 | } 251 | const link = document.createElement("link"); 252 | link.id = "font"; 253 | link.href = `https://fonts.googleapis.com/css2?family=${collapsedName}${variations}&display=swap`; 254 | link.rel = "stylesheet"; 255 | document.head.appendChild(link); 256 | }; 257 | -------------------------------------------------------------------------------- /src/BricolageGrotesque-VariableFont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arthurcloche/p5.variableFont/844e8c86f9f56430e013e9fffcb23dfb60ec0536/src/BricolageGrotesque-VariableFont.ttf -------------------------------------------------------------------------------- /src/example.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arthurcloche/p5.variableFont/844e8c86f9f56430e013e9fffcb23dfb60ec0536/src/example.mp4 -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0; 3 | padding: 0; 4 | display: flex; 5 | height: 100%; 6 | align-items: center; 7 | justify-content: center; 8 | } 9 | canvas { 10 | display: block; 11 | } 12 | --------------------------------------------------------------------------------