font2bitmap 1.0.0 → 1.1.0

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 +47 -0
  2. package/dist/cli.js +7 -6
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -44,10 +44,57 @@ font2bitmap \
44
44
  --output ./google_sans_code_symbols.h \
45
45
  --symbols '°$%'
46
46
 
47
+ # Create font with custom letter and word spacing
48
+ font2bitmap \
49
+ --fontPath ./fonts/GoogleSansCode-Medium.ttf \
50
+ --fontName google_sans_code \
51
+ --height 10 \
52
+ --output ./google_sans_code_spaced.h \
53
+ --letter-spacing 2 \
54
+ --word-spacing 4 \
55
+ --subsets ascii
56
+
47
57
  # Help
48
58
  font2bitmap --help
49
59
  ```
50
60
 
61
+ ## Formatting
62
+
63
+ The tool generates a C header file (e.g., `my_font.h`) that defines a `font_t` structure named after the `--fontName` option. This structure includes font properties such as width, height, letter spacing, word spacing, and character subsets. Subsets contain information about each character, including its bitmap representation.
64
+
65
+ Here's a brief example of what the generated C header file content looks like:
66
+
67
+ ```c
68
+ #ifndef FONT_MY_FONT_H
69
+ #define FONT_MY_FONT_H
70
+
71
+ #include <stdint.h>
72
+ #include "font.h"
73
+
74
+ const font_t my_font = {
75
+ .width = 6,
76
+ .height = 10,
77
+ .letter_spacing = 1,
78
+ .word_spacing = 6,
79
+ .subsets_count = 1,
80
+ .subsets = (const font_subset_t[]) {
81
+ {
82
+ .start = 32,
83
+ .end = 126,
84
+ .symbols_count = 95,
85
+ .symbols = (uint8_t[]){
86
+ // Bitmap data for characters
87
+ // ...
88
+ },
89
+ .offsets = (uint32_t[]) { /* ... */ },
90
+ .widths = NULL
91
+ }
92
+ }
93
+ };
94
+
95
+ #endif // FONT_MY_FONT_H
96
+ ```
97
+
51
98
  ## Limitations
52
99
 
53
100
  - The font bitmap is saved with LSB-first bit order.
package/dist/cli.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import U from"node:fs";import{Command as P}from"commander";var $={name:"font2bitmap",version:"0.0.1",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 c=e.getImageData(0,0,e.canvas.width,n),{data:s}=c,o=e.canvas.width,r=-1;for(let f=0;f<n;f++)for(let a=0;a<e.canvas.width;a++){let d=s[(f*e.canvas.width+a)*4];d!==void 0&&d<255&&(a<o&&(o=a),a>r&&(r=a))}if(o>r)return{char:t,width:0,symbol:{char:t,width:0,bitmap:new Uint8Array(0)}};let i=r-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=s[(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,r)=>o.char.charCodeAt(0)-r.char.charCodeAt(0)),n=e[0];if(!n)return[];let c=[],s={start:n.char.charCodeAt(0),end:n.char.charCodeAt(0),symbols:[n.symbol]};for(let o=1;o<e.length;o++){let r=e[o];if(!r)continue;let i=r.char.charCodeAt(0);i===s.end+1?(s.end=i,s.symbols.push(r.symbol)):(c.push(s),s={start:i,end:i,symbols:[r.symbol]})}return c.push(s),c}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 c=M(t.subsets,t.symbols),s={name:t.fontName,width:0,height:0,spacing:t.spacing,subsets:[]};if(c.length===0)return s;let o=c.map(l=>L(l,n,e)),r=o.map(({char:l,symbol:h})=>({char:l,symbol:h})),i=Math.max(0,...o.map(l=>l.width));return s.width=i,s.height=e,s.subsets=H(r),s}function M(t,e){let n={ascii:Array.from({length:95},(s,o)=>String.fromCharCode(32+o)).join(""),latin:F,digits:T,cyrillic:I,punctuation:O},c=new Set(e);return t.forEach(s=>{n[s].split("").forEach(o=>c.add(o))}),Array.from(c)}function R(t){let e=t.name,n=`FONT_${e.toUpperCase()}_H`,c=t.subsets.map(o=>{let r=0,i=[],l=[],h=[],f=(m,w,C=16,A=16)=>{if(m.length===0)return"";let g=Array.from(m).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(m=>m.width!==t.width);for(let m of o.symbols)i.push(r),r+=m.bitmap.length,l.push(m.width),h.push(f(m.bitmap,m.char,16,16));let d=(m,w=16,C=16)=>{if(m.length===0)return"{}";let A=m.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 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`{
4
4
  ${g.join(`,
5
5
  `)}
6
6
  }`},y=`{
7
- ${h.filter(m=>m!=="").join(`,
7
+ ${h.filter(c=>c!=="").join(`,
8
8
  `)}
9
9
  }`,v=`{ ${i.join(", ")} }`,S=a?`(uint8_t[]) ${d(l,20,16)}`:"NULL";return` {
10
10
  .start = ${o.start},
@@ -23,12 +23,13 @@ ${h.filter(m=>m!=="").join(`,
23
23
  const font_t ${e} = {
24
24
  .width = ${t.width},
25
25
  .height = ${t.height},
26
- .spacing = ${t.spacing},
26
+ .letter_spacing = ${t.letterSpacing},
27
+ .word_spacing = ${t.wordSpacing},
27
28
  .subsets_count = ${t.subsets.length},
28
29
  .subsets = (const font_subset_t[]) {
29
- ${c}
30
+ ${m}
30
31
  }
31
32
  };
32
33
 
33
34
  #endif // ${n}
34
- `}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("--spacing <spacing>","Letter spacing",t=>parseInt(t,10),1).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):[],spacing:p.spacing},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)}
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)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "font2bitmap",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Utility to convert font files to bitmap format",
5
5
  "main": "dist/cli.js",
6
6
  "type": "module",