font2bitmap 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -4
  2. package/dist/cli.js +14 -17
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -65,8 +65,7 @@ The tool generates a C header file (e.g., `my_font.h`) that defines a `font_t` s
65
65
  Here's a brief example of what the generated C header file content looks like:
66
66
 
67
67
  ```c
68
- #ifndef FONT_MY_FONT_H
69
- #define FONT_MY_FONT_H
68
+ #pragma once
70
69
 
71
70
  #include <stdint.h>
72
71
  #include "font.h"
@@ -91,8 +90,6 @@ const font_t my_font = {
91
90
  }
92
91
  }
93
92
  };
94
-
95
- #endif // FONT_MY_FONT_H
96
93
  ```
97
94
 
98
95
  ## Limitations
package/dist/cli.js CHANGED
@@ -1,21 +1,20 @@
1
1
  #!/usr/bin/env node
2
- import U from"node:fs";import{Command as P}from"commander";var _={name:"font2bitmap",version:"1.0.0",description:"Utility to convert font files to bitmap format",main:"dist/cli.js",type:"module",keywords:["bitmap","OLED","font","ssd1306"],author:"Pavel Koltyshev <pkoltyshev@gmail.com>",license:"MIT",repository:"pkolt/font2bitmap",bugs:{url:"https://github.com/pkolt/font2bitmap/issues",email:"pkoltyshev@gmail.com"},files:["README.md","LICENSE.md","dist/cli.js"],bin:{font2bitmap:"dist/cli.js"},engines:{node:">= 24"},scripts:{dev:"node ./src/cli.ts",build:"esbuild ./src/cli.ts --bundle --minify --platform=node --format=esm --packages=external --outfile=./dist/cli.js",types:"tsc --noEmit",format:"prettier --check src","format-fix":"prettier --write src",lint:"eslint","lint-fix":"eslint --fix",knip:"knip",test:"node --test",coverage:"node --test --experimental-test-coverage",check:"npm run types && npm run format && npm run test && npm run lint && npm run knip",prepare:'if [ "$NODE_ENV" != "production" ]; then git config core.hooksPath .git-hooks; fi',"pre-commit":"npm run check",release:"release-it"},devDependencies:{"@eslint/js":"^9.39.2","@types/node":"^25.0.3",esbuild:"^0.27.2",eslint:"^9.39.2",jiti:"^2.6.1",knip:"^5.78.0",prettier:"^3.7.4","release-it":"^19.2.2",typescript:"^5.9.3","typescript-eslint":"^8.50.1"},dependencies:{canvas:"^3.2.0",commander:"^14.0.2"}};import{createCanvas as j,registerFont as D}from"canvas";var $="Font Family",F="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",T="0123456789",I="\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041A\u041B\u041C\u041D\u041E\u041F\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042A\u042B\u042C\u042D\u042E\u042F\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043A\u043B\u043C\u043D\u043E\u043F\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044A\u044B\u044C\u044D\u044E\u044F",O=`.,!?"'-=/\\[]{}();:&*+@~`;function L(t,e,n){e.fillStyle="#fff",e.fillRect(0,0,e.canvas.width,e.canvas.height),e.fillStyle="#000",e.fillText(t,0,n/2);let m=e.getImageData(0,0,e.canvas.width,n),{data:r}=m,o=e.canvas.width,s=-1;for(let f=0;f<n;f++)for(let a=0;a<e.canvas.width;a++){let d=r[(f*e.canvas.width+a)*4];d!==void 0&&d<255&&(a<o&&(o=a),a>s&&(s=a))}if(o>s)return{char:t,width:0,symbol:{char:t,width:0,bitmap:new Uint8Array(0)}};let i=s-o+1,l=Math.ceil(i/8),h=new Uint8Array(l*n);if(i>0)for(let f=0;f<n;f++)for(let a=0;a<i;a++){let d=r[(f*e.canvas.width+(o+a))*4];if(d!==void 0&&d<128){let y=f*l+Math.floor(a/8),v=a%8,S=h[y];S!==void 0&&(h[y]=S|1<<v)}}return{char:t,width:i,symbol:{char:t,width:i,bitmap:h}}}function H(t){if(t.length===0)return[];let e=[...t].sort((o,s)=>o.char.charCodeAt(0)-s.char.charCodeAt(0)),n=e[0];if(!n)return[];let m=[],r={start:n.char.charCodeAt(0),end:n.char.charCodeAt(0),symbols:[n.symbol]};for(let o=1;o<e.length;o++){let s=e[o];if(!s)continue;let i=s.char.charCodeAt(0);i===r.end+1?(r.end=i,r.symbols.push(s.symbol)):(m.push(r),r={start:i,end:i,symbols:[s.symbol]})}return m.push(r),m}function N(t){D(t.fontPath,{family:$});let{height:e}=t,n=j(e*2,e).getContext("2d");n.font=`${e}px "${$}"`,n.textBaseline="middle",n.textAlign="left",n.textDrawingMode="glyph";let m=M(t.subsets,t.symbols),r={name:t.fontName,width:0,height:0,letterSpacing:t.letterSpacing,wordSpacing:t.wordSpacing,subsets:[]};if(m.length===0)return r;let o=m.map(l=>L(l,n,e)),s=o.map(({char:l,symbol:h})=>({char:l,symbol:h})),i=Math.max(0,...o.map(l=>l.width));return r.width=i,r.height=e,r.subsets=H(s),r}function M(t,e){let n={ascii:Array.from({length:95},(r,o)=>String.fromCharCode(32+o)).join(""),latin:F,digits:T,cyrillic:I,punctuation:O},m=new Set(e);return t.forEach(r=>{n[r].split("").forEach(o=>m.add(o))}),Array.from(m)}function R(t){let e=t.name,n=`FONT_${e.toUpperCase()}_H`,m=t.subsets.map(o=>{let s=0,i=[],l=[],h=[],f=(c,w,C=16,A=16)=>{if(c.length===0)return"";let g=Array.from(c).map(b=>`0x${b.toString(16).padStart(2,"0")}`),u=[];for(let b=0;b<g.length;b+=C)u.push(" ".repeat(A)+g.slice(b,b+C).join(", "));let E=u.pop();return E&&u.push(`${E},`),`${u.join(`,
3
- `)} // '${w}'`},a=o.symbols.some(c=>c.width!==t.width);for(let c of o.symbols)i.push(s),s+=c.bitmap.length,l.push(c.width),h.push(f(c.bitmap,c.char,16,16));let d=(c,w=16,C=16)=>{if(c.length===0)return"{}";let A=c.map(u=>`0x${u.toString(16).padStart(2,"0")}`),g=[];for(let u=0;u<A.length;u+=w)g.push(" ".repeat(C)+A.slice(u,u+w).join(", "));return`{
2
+ import R from"node:fs";import{Command as P}from"commander";var v={name:"font2bitmap",version:"1.1.2",description:"Utility to convert font files to bitmap format",main:"dist/cli.js",type:"module",keywords:["bitmap","OLED","font","ssd1306"],author:"Pavel Koltyshev <pkoltyshev@gmail.com>",license:"MIT",repository:"pkolt/font2bitmap",bugs:{url:"https://github.com/pkolt/font2bitmap/issues",email:"pkoltyshev@gmail.com"},files:["README.md","LICENSE.md","dist/cli.js"],bin:{font2bitmap:"dist/cli.js"},engines:{node:">= 24"},scripts:{dev:"node ./src/cli.ts",build:"rimraf dist && esbuild ./src/cli.ts --bundle --minify --platform=node --format=esm --packages=external --outfile=./dist/cli.js",types:"tsc --noEmit",format:"prettier --check src","format-fix":"prettier --write src",lint:"eslint","lint-fix":"eslint --fix",knip:"knip",test:"node --test",coverage:"node --test --experimental-test-coverage",check:"npm run types && npm run format && npm run test && npm run lint && npm run knip",prepare:'if [ "$NODE_ENV" != "production" ]; then git config core.hooksPath .git-hooks; fi',"pre-commit":"npm run check",release:"release-it"},devDependencies:{"@eslint/js":"^9.39.2","@types/node":"^25.0.3",esbuild:"^0.27.2",eslint:"^9.39.2",jiti:"^2.6.1",knip:"^5.78.0",prettier:"^3.7.4","release-it":"^19.2.2",rimraf:"^6.1.3",typescript:"^5.9.3","typescript-eslint":"^8.50.1"},dependencies:{canvas:"^3.2.0",commander:"^14.0.2"}};import{createCanvas as k,registerFont as j}from"canvas";var _="Font Family",E="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",F="0123456789",I="\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041A\u041B\u041C\u041D\u041E\u041F\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042A\u042B\u042C\u042D\u042E\u042F\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043A\u043B\u043C\u043D\u043E\u043F\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044A\u044B\u044C\u044D\u044E\u044F",T=`.,!?"'-=/\\[]{}();:&*+@~`;function D(t,e,o){e.fillStyle="#fff",e.fillRect(0,0,e.canvas.width,e.canvas.height),e.fillStyle="#000",e.fillText(t,0,o/2);let p=e.getImageData(0,0,e.canvas.width,o),{data:n}=p,r=e.canvas.width,i=-1;for(let f=0;f<o;f++)for(let c=0;c<e.canvas.width;c++){let d=n[(f*e.canvas.width+c)*4];d!==void 0&&d<255&&(c<r&&(r=c),c>i&&(i=c))}if(r>i)return{char:t,width:0,symbol:{char:t,width:0,bitmap:new Uint8Array(0)}};let a=i-r+1,l=Math.ceil(a/8),h=new Uint8Array(l*o);if(a>0)for(let f=0;f<o;f++)for(let c=0;c<a;c++){let d=n[(f*e.canvas.width+(r+c))*4];if(d!==void 0&&d<128){let y=f*l+Math.floor(c/8),x=c%8,s=h[y];s!==void 0&&(h[y]=s|1<<x)}}return{char:t,width:a,symbol:{char:t,width:a,bitmap:h}}}function L(t){if(t.length===0)return[];let e=[...t].sort((r,i)=>r.char.charCodeAt(0)-i.char.charCodeAt(0)),o=e[0];if(!o)return[];let p=[],n={start:o.char.charCodeAt(0),end:o.char.charCodeAt(0),symbols:[o.symbol]};for(let r=1;r<e.length;r++){let i=e[r];if(!i)continue;let a=i.char.charCodeAt(0);a===n.end+1?(n.end=a,n.symbols.push(i.symbol)):(p.push(n),n={start:a,end:a,symbols:[i.symbol]})}return p.push(n),p}function O(t){j(t.fontPath,{family:_});let{height:e}=t,o=k(e*2,e).getContext("2d");o.font=`${e}px "${_}"`,o.textBaseline="middle",o.textAlign="left",o.textDrawingMode="glyph";let p=M(t.subsets,t.symbols),n={name:t.fontName,width:0,height:0,letterSpacing:t.letterSpacing,wordSpacing:t.wordSpacing,subsets:[]};if(p.length===0)return n;let r=p.map(l=>D(l,o,e)),i=r.map(({char:l,symbol:h})=>({char:l,symbol:h})),a=Math.max(0,...r.map(l=>l.width));return n.width=a,n.height=e,n.subsets=L(i),n}function M(t,e){let o={ascii:Array.from({length:95},(n,r)=>String.fromCharCode(32+r)).join(""),latin:E,digits:F,cyrillic:I,punctuation:T},p=new Set(e);return t.forEach(n=>{o[n].split("").forEach(r=>p.add(r))}),Array.from(p)}function N(t){let e=t.name,o=t.subsets.map(n=>{let r=0,i=[],a=[],l=[],h=(s,S,w=16,C=16)=>{if(s.length===0)return"";let g=Array.from(s).map(b=>`0x${b.toString(16).padStart(2,"0")}`),u=[];for(let b=0;b<g.length;b+=w)u.push(" ".repeat(C)+g.slice(b,b+w).join(", "));let $=u.pop();return $&&u.push(`${$},`),`${u.join(`,
3
+ `)} // '${S}'`},f=n.symbols.some(s=>s.width!==t.width);for(let s of n.symbols)i.push(r),r+=s.bitmap.length,a.push(s.width),l.push(h(s.bitmap,s.char,16,16));let c=(s,S=16,w=16)=>{if(s.length===0)return"{}";let C=s.map(u=>`0x${u.toString(16).padStart(2,"0")}`),g=[];for(let u=0;u<C.length;u+=S)g.push(" ".repeat(w)+C.slice(u,u+S).join(", "));return`{
4
4
  ${g.join(`,
5
5
  `)}
6
- }`},y=`{
7
- ${h.filter(c=>c!=="").join(`,
6
+ }`},d=`{
7
+ ${l.filter(s=>s!=="").join(`,
8
8
  `)}
9
- }`,v=`{ ${i.join(", ")} }`,S=a?`(uint8_t[]) ${d(l,20,16)}`:"NULL";return` {
10
- .start = ${o.start},
11
- .end = ${o.end},
12
- .symbols_count = ${o.symbols.length},
13
- .symbols = (uint8_t[])${y},
14
- .offsets = (uint32_t[]) ${v},
15
- .widths = ${S}
9
+ }`,y=`{ ${i.join(", ")} }`,x=f?`(uint8_t[]) ${c(a,20,16)}`:"NULL";return` {
10
+ .start = ${n.start},
11
+ .end = ${n.end},
12
+ .symbols_count = ${n.symbols.length},
13
+ .symbols = (uint8_t[])${d},
14
+ .offsets = (uint32_t[]) ${y},
15
+ .widths = ${x}
16
16
  }`}).join(`,
17
- `);return`#ifndef ${n}
18
- #define ${n}
17
+ `);return`#pragma once
19
18
 
20
19
  #include <stdint.h>
21
20
  #include "font.h"
@@ -27,9 +26,7 @@ const font_t ${e} = {
27
26
  .word_spacing = ${t.wordSpacing},
28
27
  .subsets_count = ${t.subsets.length},
29
28
  .subsets = (const font_subset_t[]) {
30
- ${m}
29
+ ${o}
31
30
  }
32
31
  };
33
-
34
- #endif // ${n}
35
- `}var x=new P;x.name("font2bitmap").version(_.version).description(_.description);x.requiredOption("--fontPath <path>","Path to the font file (e.g., RobotoMono-Medium.ttf)").requiredOption("--fontName <name>","Name of the font (e.g., RobotoMono)").requiredOption("--height <height>","Height of the font in pixels",t=>parseInt(t,10)).requiredOption("--output <path>","Output file path").option("--format <format>","Output format","pico").option("--subsets <subset1,subset2>","Comma-separated list of character subsets",t=>t.split(","),["ascii"]).option("--letter-spacing <spacing>","Letter spacing",t=>parseInt(t,10),1).option("--word-spacing <spacing>","Word spacing",t=>parseInt(t,10),6).option("--symbols <value>","A string of additional characters to include","");x.parse(process.argv);var p=x.opts();try{if(!U.existsSync(p.fontPath))throw new Error(`Error: Font file not found at ${p.fontPath}`);let t={fontPath:p.fontPath,fontName:p.fontName,height:p.height,subsets:p.subsets,symbols:p.symbols?Array.from(p.symbols):[],letterSpacing:p.letterSpacing,wordSpacing:p.wordSpacing},e=N(t);if(p.format==="pico"){let n=R(e);U.writeFileSync(p.output,n),console.log(`Output successfully written to ${p.output}`)}else throw new Error(`Error: Unsupported format "${p.format}"`)}catch(t){t instanceof Error?console.error(t.message):console.error(t),process.exit(1)}
32
+ `}var A=new P;A.name("font2bitmap").version(v.version).description(v.description);A.requiredOption("--fontPath <path>","Path to the font file (e.g., RobotoMono-Medium.ttf)").requiredOption("--fontName <name>","Name of the font (e.g., RobotoMono)").requiredOption("--height <height>","Height of the font in pixels",t=>parseInt(t,10)).requiredOption("--output <path>","Output file path").option("--format <format>","Output format","pico").option("--subsets <subset1,subset2>","Comma-separated list of character subsets",t=>t.split(","),["ascii"]).option("--letter-spacing <spacing>","Letter spacing",t=>parseInt(t,10),1).option("--word-spacing <spacing>","Word spacing",t=>parseInt(t,10),6).option("--symbols <value>","A string of additional characters to include","");A.parse(process.argv);var m=A.opts();try{if(!R.existsSync(m.fontPath))throw new Error(`Error: Font file not found at ${m.fontPath}`);let t={fontPath:m.fontPath,fontName:m.fontName,height:m.height,subsets:m.subsets,symbols:m.symbols?Array.from(m.symbols):[],letterSpacing:m.letterSpacing,wordSpacing:m.wordSpacing},e=O(t);if(m.format==="pico"){let o=N(e);R.writeFileSync(m.output,o),console.log(`Output successfully written to ${m.output}`)}else throw new Error(`Error: Unsupported format "${m.format}"`)}catch(t){t instanceof Error?console.error(t.message):console.error(t),process.exit(1)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "font2bitmap",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Utility to convert font files to bitmap format",
5
5
  "main": "dist/cli.js",
6
6
  "type": "module",
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "scripts": {
32
32
  "dev": "node ./src/cli.ts",
33
- "build": "esbuild ./src/cli.ts --bundle --minify --platform=node --format=esm --packages=external --outfile=./dist/cli.js",
33
+ "build": "rimraf dist && esbuild ./src/cli.ts --bundle --minify --platform=node --format=esm --packages=external --outfile=./dist/cli.js",
34
34
  "types": "tsc --noEmit",
35
35
  "format": "prettier --check src",
36
36
  "format-fix": "prettier --write src",
@@ -53,6 +53,7 @@
53
53
  "knip": "^5.78.0",
54
54
  "prettier": "^3.7.4",
55
55
  "release-it": "^19.2.2",
56
+ "rimraf": "^6.1.3",
56
57
  "typescript": "^5.9.3",
57
58
  "typescript-eslint": "^8.50.1"
58
59
  },