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.
- package/README.md +47 -0
- package/dist/cli.js +7 -6
- 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
|
|
3
|
-
`)} // '${w}'`},a=o.symbols.some(
|
|
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(
|
|
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
|
-
.
|
|
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
|
-
${
|
|
30
|
+
${m}
|
|
30
31
|
}
|
|
31
32
|
};
|
|
32
33
|
|
|
33
34
|
#endif // ${n}
|
|
34
|
-
`}var x=new P;x.name("font2bitmap").version(
|
|
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)}
|