From dcd3f833a0640e56dbd1fd3b224bdc7fff6f07db Mon Sep 17 00:00:00 2001 From: Federico Jacobi Date: Thu, 12 Jun 2025 13:10:20 -0400 Subject: [PATCH 1/6] Smaller more efficient rendering --- src/index.js | 39 ++++++++++++++++----------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/src/index.js b/src/index.js index 00ef1b2..d05afcf 100644 --- a/src/index.js +++ b/src/index.js @@ -11,32 +11,25 @@ export const initFont = ({ height=DEFAULT_CHAR_HEIGHT, ...chars }={}, ctx) => { return } - const bin2arr = (bin, width) => bin.match(new RegExp(`.{${width}}`, 'g')); - const isNumber = code => code > 0; - return (string, x=0, y=0, size=24, color=DEFAULT_COLOR) => { + const pixelSize = size / height; const renderChar = (charX, char) => { - const pixelSize = size/height; const fontCode = chars[char.charCodeAt()] || ''; - const binaryChar = isNumber(fontCode) ? fontCode : fontCode.codePointAt(); - - const binary = (binaryChar || 0).toString(2); - - const width = Math.ceil(binary.length / height); - const marginX = charX + pixelSize; - const formattedBinary = binary.padStart(width * height, 0); - const binaryCols = bin2arr(formattedBinary, height); - - console.debug('Rendering char', char, char.charCodeAt(), fontCode, binaryChar, binaryCols); - - binaryCols.map((column, colPos) => - [...column].map((pixel, pixPos) => { - ctx.fillStyle = !+pixel ? 'transparent' : color; // pixel == 0 ? - ctx.fillRect(x + marginX + colPos * pixelSize, y + pixPos * pixelSize, pixelSize, pixelSize); - }) - ); - - return charX + (width+1)*pixelSize + let bin = Number.isInteger( fontCode ) ? fontCode : fontCode.codePointAt(); + const binary = ( bin || 0 ).toString( 2 ); + const width = Math.ceil( binary.length / height ); + + ctx.fillStyle = color; + for ( let col = width; col > 0; col-- ) { + for ( let row = height; row > 0 && bin > 0; row-- ) { + if ( bin & 1 ) { + ctx.fillRect( x + charX + col * pixelSize, y + row * pixelSize, pixelSize, pixelSize ); + } + bin >>= 1 + } + } + + return charX + ( width + 1 ) * pixelSize; }; console.debug('Rendering string', string); From aaeee2523b62ac3791a55630f3ae1ff3f87fd2bc Mon Sep 17 00:00:00 2001 From: Federico Jacobi Date: Thu, 12 Jun 2025 13:30:06 -0400 Subject: [PATCH 2/6] Replace if with and --- src/index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index d05afcf..745fcd3 100644 --- a/src/index.js +++ b/src/index.js @@ -22,9 +22,7 @@ export const initFont = ({ height=DEFAULT_CHAR_HEIGHT, ...chars }={}, ctx) => { ctx.fillStyle = color; for ( let col = width; col > 0; col-- ) { for ( let row = height; row > 0 && bin > 0; row-- ) { - if ( bin & 1 ) { - ctx.fillRect( x + charX + col * pixelSize, y + row * pixelSize, pixelSize, pixelSize ); - } + bin & 1 && ctx.fillRect( x + charX + col * pixelSize, y + row * pixelSize, pixelSize, pixelSize ); bin >>= 1 } } From ed5e3407e76900014613f6bde27ae073b0d7e0d7 Mon Sep 17 00:00:00 2001 From: Federico Jacobi Date: Thu, 12 Jun 2025 13:33:33 -0400 Subject: [PATCH 3/6] add dist --- dist/index.js | 37 +++++++++++++-------------------- dist/tinyfont-font-pixel.min.js | 2 +- dist/tinyfont-font-tiny.min.js | 2 +- 3 files changed, 16 insertions(+), 25 deletions(-) diff --git a/dist/index.js b/dist/index.js index d795876..5217389 100644 --- a/dist/index.js +++ b/dist/index.js @@ -13,32 +13,23 @@ const initFont = ({ height=DEFAULT_CHAR_HEIGHT, ...chars }={}, ctx) => { return } - const bin2arr = (bin, width) => bin.match(new RegExp(`.{${width}}`, 'g')); - const isNumber = code => code > 0; - return (string, x=0, y=0, size=24, color=DEFAULT_COLOR) => { + const pixelSize = size / height; const renderChar = (charX, char) => { - const pixelSize = size/height; const fontCode = chars[char.charCodeAt()] || ''; - const binaryChar = isNumber(fontCode) ? fontCode : fontCode.codePointAt(); - - const binary = (binaryChar || 0).toString(2); - - const width = Math.ceil(binary.length / height); - const marginX = charX + pixelSize; - const formattedBinary = binary.padStart(width * height, 0); - const binaryCols = bin2arr(formattedBinary, height); - - console.debug('Rendering char', char, char.charCodeAt(), fontCode, binaryChar, binaryCols); - - binaryCols.map((column, colPos) => - [...column].map((pixel, pixPos) => { - ctx.fillStyle = !+pixel ? 'transparent' : color; // pixel == 0 ? - ctx.fillRect(x + marginX + colPos * pixelSize, y + pixPos * pixelSize, pixelSize, pixelSize); - }) - ); - - return charX + (width+1)*pixelSize + let bin = Number.isInteger( fontCode ) ? fontCode : fontCode.codePointAt(); + const binary = ( bin || 0 ).toString( 2 ); + const width = Math.ceil( binary.length / height ); + + ctx.fillStyle = color; + for ( let col = width; col > 0; col-- ) { + for ( let row = height; row > 0 && bin > 0; row-- ) { + bin & 1 && ctx.fillRect( x + charX + col * pixelSize, y + row * pixelSize, pixelSize, pixelSize ); + bin >>= 1; + } + } + + return charX + ( width + 1 ) * pixelSize; }; console.debug('Rendering string', string); diff --git a/dist/tinyfont-font-pixel.min.js b/dist/tinyfont-font-pixel.min.js index 61416b0..bb025ea 100644 --- a/dist/tinyfont-font-pixel.min.js +++ b/dist/tinyfont-font-pixel.min.js @@ -1 +1 @@ -"use strict";const t=5,e="#000",r=({height:t=5,...e}={},r)=>{if(e&&r)return(n,o=0,a=0,c=24,i="#000")=>{[...n].reduce((n,s)=>{const l=c/t,p=e[s.charCodeAt()]||"",h=(((t=>t>0)(p)?p:p.codePointAt())||0).toString(2),g=Math.ceil(h.length/t),d=n+l;return((t,e)=>t.match(new RegExp(`.{${e}}`,"g")))(h.padStart(g*t,0),t).map((t,e)=>[...t].map((t,n)=>{r.fillStyle=+t?i:"transparent",r.fillRect(o+d+e*l,a+n*l,l,l)})),n+(g+1)*l},0)}};exports.initFont=r;const n=[...Array(33),29,,,,,,12,,,,"ᇄ",3,"ႄ",1,1118480,"縿",31,"庽","嚿","炟","皷","纷","䈟","线","皿",17,,,"⥊",,"䊼",,"㹏","纮","縱","縮","纵","纐","񴚦","粟","䟱","丿",1020241,"簡",33059359,1024159,"縿","纜","񼙯","繍","皷","䏰","簿",25363672,32541759,18157905,"惸",18470705,,,,,"С"];exports.font=n; \ No newline at end of file +"use strict";const t=5,e="#000",r=({height:t=5,...e}={},r)=>{if(e&&r)return(o,n=0,c=0,i=24,l="#000")=>{const s=i/t;[...o].reduce((o,i)=>{const f=e[i.charCodeAt()]||"";let h=Number.isInteger(f)?f:f.codePointAt();const u=(h||0).toString(2),g=Math.ceil(u.length/t);r.fillStyle=l;for(let e=g;e>0;e--)for(let i=t;i>0&&h>0;i--)1&h&&r.fillRect(n+o+e*s,c+i*s,s,s),h>>=1;return o+(g+1)*s},0)}};exports.initFont=r;const o=[...Array(33),29,,,,,,12,,,,"ᇄ",3,"ႄ",1,1118480,"縿",31,"庽","嚿","炟","皷","纷","䈟","线","皿",17,,,"⥊",,"䊼",,"㹏","纮","縱","縮","纵","纐","񴚦","粟","䟱","丿",1020241,"簡",33059359,1024159,"縿","纜","񼙯","繍","皷","䏰","簿",25363672,32541759,18157905,"惸",18470705,,,,,"С"];exports.font=o; \ No newline at end of file diff --git a/dist/tinyfont-font-tiny.min.js b/dist/tinyfont-font-tiny.min.js index 19a069e..6e79f80 100644 --- a/dist/tinyfont-font-tiny.min.js +++ b/dist/tinyfont-font-tiny.min.js @@ -1 +1 @@ -"use strict";const t=5,e="#000",r=({height:t=5,...e}={},r)=>{if(e&&r)return(n,i=0,o=0,a=24,c="#000")=>{[...n].reduce((n,h)=>{const s=a/t,l=e[h.charCodeAt()]||"",p=(((t=>t>0)(l)?l:l.codePointAt())||0).toString(2),g=Math.ceil(p.length/t),u=n+s;return((t,e)=>t.match(new RegExp(`.{${e}}`,"g")))(p.padStart(g*t,0),t).map((t,e)=>[...t].map((t,n)=>{r.fillStyle=+t?c:"transparent",r.fillRect(i+u+e*s,o+n*s,s,s)})),n+(g+1)*s},0)}};exports.initFont=r;const n=[...Array(33),13,,,,,,12,,,,"ɲ",3,"Ȣ",1,"Ĥ","ྟ","","஝","য়","ฯ","ඛ","ྒྷ","ࢼ","࿟","ඟ",9,,,"Օ",,"࢜",,"͗","བྷ","u","ݟ","ݖ","ݤ","ݓ","ܧ",11,27,"༥",15,476995,"݇","ݗ","ݦ","畱","t","Ŵ","ô","ܗ","ؖ",398870,"ԥ","ا","ѱ",,,,,273];n.height=4,exports.font=n; \ No newline at end of file +"use strict";const t=5,e="#000",r=({height:t=5,...e}={},r)=>{if(e&&r)return(o,n=0,i=0,c=24,l="#000")=>{const s=c/t;[...o].reduce((o,c)=>{const h=e[c.charCodeAt()]||"";let f=Number.isInteger(h)?h:h.codePointAt();const u=(f||0).toString(2),g=Math.ceil(u.length/t);r.fillStyle=l;for(let e=g;e>0;e--)for(let c=t;c>0&&f>0;c--)1&f&&r.fillRect(n+o+e*s,i+c*s,s,s),f>>=1;return o+(g+1)*s},0)}};exports.initFont=r;const o=[...Array(33),13,,,,,,12,,,,"ɲ",3,"Ȣ",1,"Ĥ","ྟ","","஝","য়","ฯ","ඛ","ྒྷ","ࢼ","࿟","ඟ",9,,,"Օ",,"࢜",,"͗","བྷ","u","ݟ","ݖ","ݤ","ݓ","ܧ",11,27,"༥",15,476995,"݇","ݗ","ݦ","畱","t","Ŵ","ô","ܗ","ؖ",398870,"ԥ","ا","ѱ",,,,,273];o.height=4,exports.font=o; \ No newline at end of file From 88d1b96567ba37899636722d61e57641c33c55ce Mon Sep 17 00:00:00 2001 From: Federico Jacobi Date: Mon, 16 Jun 2025 07:47:31 -0400 Subject: [PATCH 4/6] Skip more blank cells --- src/index.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index 745fcd3..877b2a1 100644 --- a/src/index.js +++ b/src/index.js @@ -18,13 +18,16 @@ export const initFont = ({ height=DEFAULT_CHAR_HEIGHT, ...chars }={}, ctx) => { let bin = Number.isInteger( fontCode ) ? fontCode : fontCode.codePointAt(); const binary = ( bin || 0 ).toString( 2 ); const width = Math.ceil( binary.length / height ); + const mask = ( 1 << height ) - 1; ctx.fillStyle = color; for ( let col = width; col > 0; col-- ) { - for ( let row = height; row > 0 && bin > 0; row-- ) { - bin & 1 && ctx.fillRect( x + charX + col * pixelSize, y + row * pixelSize, pixelSize, pixelSize ); - bin >>= 1 + let rowPos = height; + for ( let row = bin & mask; row > 0; row >>= 1 ) { + row & 1 && ctx.fillRect( x + charX + col * pixelSize, y + rowPos * pixelSize, pixelSize, pixelSize ); + rowPos--; } + bin >>= height; } return charX + ( width + 1 ) * pixelSize; From 3450eef1c5bc441757dd8b9a2cef4d9416ddc9de Mon Sep 17 00:00:00 2001 From: Federico Jacobi Date: Mon, 16 Jun 2025 13:45:32 -0400 Subject: [PATCH 5/6] A few bytes larger renderer, but vastly more efficient --- dist/index.js | 26 ++++++++++++++++++++------ dist/tinyfont-font-pixel.min.js | 2 +- dist/tinyfont-font-tiny.min.js | 2 +- src/index.js | 21 ++++++++++++++++----- 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/dist/index.js b/dist/index.js index 5217389..bb00ba3 100644 --- a/dist/index.js +++ b/dist/index.js @@ -13,20 +13,34 @@ const initFont = ({ height=DEFAULT_CHAR_HEIGHT, ...chars }={}, ctx) => { return } - return (string, x=0, y=0, size=24, color=DEFAULT_COLOR) => { + return ( string, x = 0, y = 0, size = 24, color = DEFAULT_COLOR ) => { const pixelSize = size / height; - const renderChar = (charX, char) => { - const fontCode = chars[char.charCodeAt()] || ''; + const renderChar = ( charX, char ) => { + const fontCode = chars[ char.charCodeAt() ] || ''; let bin = Number.isInteger( fontCode ) ? fontCode : fontCode.codePointAt(); const binary = ( bin || 0 ).toString( 2 ); const width = Math.ceil( binary.length / height ); + const mask = ( 1 << height ) - 1; ctx.fillStyle = color; for ( let col = width; col > 0; col-- ) { - for ( let row = height; row > 0 && bin > 0; row-- ) { - bin & 1 && ctx.fillRect( x + charX + col * pixelSize, y + row * pixelSize, pixelSize, pixelSize ); - bin >>= 1; + let rowPos = height - 1; + for ( let row = bin & mask; row > 0; row >>= 1 ) { + if ( row & 1 ) { + let y1 = y + rowPos * pixelSize, h = pixelSize; + + while ( ( row >> 1 ) & 1 ) { + y1 -= pixelSize; + h += pixelSize; + row >>= 1; + rowPos--; + } + + ctx.fillRect( x + charX + col * pixelSize, y1, pixelSize, h ); + } + rowPos--; } + bin >>= height; } return charX + ( width + 1 ) * pixelSize; diff --git a/dist/tinyfont-font-pixel.min.js b/dist/tinyfont-font-pixel.min.js index bb025ea..6083773 100644 --- a/dist/tinyfont-font-pixel.min.js +++ b/dist/tinyfont-font-pixel.min.js @@ -1 +1 @@ -"use strict";const t=5,e="#000",r=({height:t=5,...e}={},r)=>{if(e&&r)return(o,n=0,c=0,i=24,l="#000")=>{const s=i/t;[...o].reduce((o,i)=>{const f=e[i.charCodeAt()]||"";let h=Number.isInteger(f)?f:f.codePointAt();const u=(h||0).toString(2),g=Math.ceil(u.length/t);r.fillStyle=l;for(let e=g;e>0;e--)for(let i=t;i>0&&h>0;i--)1&h&&r.fillRect(n+o+e*s,c+i*s,s,s),h>>=1;return o+(g+1)*s},0)}};exports.initFont=r;const o=[...Array(33),29,,,,,,12,,,,"ᇄ",3,"ႄ",1,1118480,"縿",31,"庽","嚿","炟","皷","纷","䈟","线","皿",17,,,"⥊",,"䊼",,"㹏","纮","縱","縮","纵","纐","񴚦","粟","䟱","丿",1020241,"簡",33059359,1024159,"縿","纜","񼙯","繍","皷","䏰","簿",25363672,32541759,18157905,"惸",18470705,,,,,"С"];exports.font=o; \ No newline at end of file +"use strict";const t=5,e="#000",r=({height:t=5,...e}={},r)=>{if(e&&r)return(o,n=0,i=0,l=24,c="#000")=>{const s=l/t;[...o].reduce((o,l)=>{const f=e[l.charCodeAt()]||"";let h=Number.isInteger(f)?f:f.codePointAt();const u=(h||0).toString(2),g=Math.ceil(u.length/t),a=(1<0;e--){let l=t-1;for(let t=h&a;t>0;t>>=1){if(1&t){let c=i+l*s,f=s;for(;t>>1&1;)c-=s,f+=s,t>>=1,l--;r.fillRect(n+o+e*s,c,s,f)}l--}h>>=t}return o+(g+1)*s},0)}};exports.initFont=r;const o=[...Array(33),29,,,,,,12,,,,"ᇄ",3,"ႄ",1,1118480,"縿",31,"庽","嚿","炟","皷","纷","䈟","线","皿",17,,,"⥊",,"䊼",,"㹏","纮","縱","縮","纵","纐","񴚦","粟","䟱","丿",1020241,"簡",33059359,1024159,"縿","纜","񼙯","繍","皷","䏰","簿",25363672,32541759,18157905,"惸",18470705,,,,,"С"];exports.font=o; \ No newline at end of file diff --git a/dist/tinyfont-font-tiny.min.js b/dist/tinyfont-font-tiny.min.js index 6e79f80..bfec4dc 100644 --- a/dist/tinyfont-font-tiny.min.js +++ b/dist/tinyfont-font-tiny.min.js @@ -1 +1 @@ -"use strict";const t=5,e="#000",r=({height:t=5,...e}={},r)=>{if(e&&r)return(o,n=0,i=0,c=24,l="#000")=>{const s=c/t;[...o].reduce((o,c)=>{const h=e[c.charCodeAt()]||"";let f=Number.isInteger(h)?h:h.codePointAt();const u=(f||0).toString(2),g=Math.ceil(u.length/t);r.fillStyle=l;for(let e=g;e>0;e--)for(let c=t;c>0&&f>0;c--)1&f&&r.fillRect(n+o+e*s,i+c*s,s,s),f>>=1;return o+(g+1)*s},0)}};exports.initFont=r;const o=[...Array(33),13,,,,,,12,,,,"ɲ",3,"Ȣ",1,"Ĥ","ྟ","","஝","য়","ฯ","ඛ","ྒྷ","ࢼ","࿟","ඟ",9,,,"Օ",,"࢜",,"͗","བྷ","u","ݟ","ݖ","ݤ","ݓ","ܧ",11,27,"༥",15,476995,"݇","ݗ","ݦ","畱","t","Ŵ","ô","ܗ","ؖ",398870,"ԥ","ا","ѱ",,,,,273];o.height=4,exports.font=o; \ No newline at end of file +"use strict";const t=5,e="#000",r=({height:t=5,...e}={},r)=>{if(e&&r)return(o,n=0,i=0,l=24,c="#000")=>{const s=l/t;[...o].reduce((o,l)=>{const f=e[l.charCodeAt()]||"";let h=Number.isInteger(f)?f:f.codePointAt();const u=(h||0).toString(2),g=Math.ceil(u.length/t),a=(1<0;e--){let l=t-1;for(let t=h&a;t>0;t>>=1){if(1&t){let c=i+l*s,f=s;for(;t>>1&1;)c-=s,f+=s,t>>=1,l--;r.fillRect(n+o+e*s,c,s,f)}l--}h>>=t}return o+(g+1)*s},0)}};exports.initFont=r;const o=[...Array(33),13,,,,,,12,,,,"ɲ",3,"Ȣ",1,"Ĥ","ྟ","","஝","য়","ฯ","ඛ","ྒྷ","ࢼ","࿟","ඟ",9,,,"Օ",,"࢜",,"͗","བྷ","u","ݟ","ݖ","ݤ","ݓ","ܧ",11,27,"༥",15,476995,"݇","ݗ","ݦ","畱","t","Ŵ","ô","ܗ","ؖ",398870,"ԥ","ا","ѱ",,,,,273];o.height=4,exports.font=o; \ No newline at end of file diff --git a/src/index.js b/src/index.js index 877b2a1..8515155 100644 --- a/src/index.js +++ b/src/index.js @@ -11,10 +11,10 @@ export const initFont = ({ height=DEFAULT_CHAR_HEIGHT, ...chars }={}, ctx) => { return } - return (string, x=0, y=0, size=24, color=DEFAULT_COLOR) => { + return ( string, x = 0, y = 0, size = 24, color = DEFAULT_COLOR ) => { const pixelSize = size / height; - const renderChar = (charX, char) => { - const fontCode = chars[char.charCodeAt()] || ''; + const renderChar = ( charX, char ) => { + const fontCode = chars[ char.charCodeAt() ] || ''; let bin = Number.isInteger( fontCode ) ? fontCode : fontCode.codePointAt(); const binary = ( bin || 0 ).toString( 2 ); const width = Math.ceil( binary.length / height ); @@ -22,9 +22,20 @@ export const initFont = ({ height=DEFAULT_CHAR_HEIGHT, ...chars }={}, ctx) => { ctx.fillStyle = color; for ( let col = width; col > 0; col-- ) { - let rowPos = height; + let rowPos = height - 1; for ( let row = bin & mask; row > 0; row >>= 1 ) { - row & 1 && ctx.fillRect( x + charX + col * pixelSize, y + rowPos * pixelSize, pixelSize, pixelSize ); + if ( row & 1 ) { + let y1 = y + rowPos * pixelSize, h = pixelSize; + + while ( ( row >> 1 ) & 1 ) { + y1 -= pixelSize; + h += pixelSize; + row >>= 1; + rowPos--; + } + + ctx.fillRect( x + charX + col * pixelSize, y1, pixelSize, h ); + } rowPos--; } bin >>= height; From fa8c1b9e98d8c8a27c80f49d370c9368b710bb3c Mon Sep 17 00:00:00 2001 From: Federico Jacobi Date: Mon, 16 Jun 2025 13:49:24 -0400 Subject: [PATCH 6/6] Add comment to while loop optimization --- src/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/index.js b/src/index.js index 8515155..e59bb60 100644 --- a/src/index.js +++ b/src/index.js @@ -27,6 +27,8 @@ export const initFont = ({ height=DEFAULT_CHAR_HEIGHT, ...chars }={}, ctx) => { if ( row & 1 ) { let y1 = y + rowPos * pixelSize, h = pixelSize; + // This while loop greatly reduces the amount of draw calls. + // If you need a few more bytes, you could remove this. while ( ( row >> 1 ) & 1 ) { y1 -= pixelSize; h += pixelSize;