Skip to content

Commit ffc5138

Browse files
committed
Improve BDF font import (character positions)
PBF now export doesn't store empty column at end of char JS export now correctly uses 'advance' and handles missing chars correctly
1 parent 386937b commit ffc5138

File tree

1 file changed

+65
-44
lines changed

1 file changed

+65
-44
lines changed

fontconverter.js

Lines changed: 65 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -186,14 +186,16 @@ FontGlyph.prototype.appendBits = function(bits, info) {
186186
};
187187

188188
FontGlyph.prototype.debug = function() {
189-
var map = ".#";//"░█";
189+
var map = "░█";
190190
if (this.font.bpp==2) map = "░▒▓█";
191191
var debugText = [];
192-
for (var y=0;y<this.font.fmHeight;y++) debugText.push("");
193-
for (var x=this.xStart;x<=this.xEnd;x++) {
194-
for (var y=0;y<this.font.fmHeight;y++) {
195-
var col = this.getPixel(x,y);
196-
debugText[y] += (y>=this.yStart && y<=this.yEnd) ? map[col] : ".";
192+
for (var y=0;y<this.font.fmHeight;y++) {
193+
debugText[y]="";
194+
for (var x=0;x<this.advance;x++) {
195+
var px = ".";
196+
if (x>=this.xStart && x<=this.xEnd && y>=this.yStart && y<=this.yEnd)
197+
px = map[this.getPixel(x,y)];
198+
debugText[y] += px;
197199
}
198200
}
199201
console.log("charcode ", this.ch);
@@ -229,13 +231,14 @@ FontGlyph.prototype.appendBits = function(bits, info) {
229231
if (ch != 32) return undefined; // if it's empty and not a space, ignore it!
230232
xStart=0;
231233
xEnd = this.fmWidth >> 1; // treat spaces as half-width
232-
} else if (xEnd<this.fmWidth-1)
233-
xEnd += this.glyphPadX; // if not full width, add a space after
234+
}
234235
}
235236
glyph.width = xEnd+1-xStart;
236237
glyph.xStart = xStart;
237238
glyph.xEnd = xEnd;
238239
glyph.advance = glyph.width;
240+
if (xEnd<this.fmWidth-1)
241+
glyph.advance += this.glyphPadX; // if not full width, add a space after
239242
if (!this.glyphPadX) glyph.advance++; // hack - add once space of padding
240243

241244
if (this.fullHeight) {
@@ -351,17 +354,14 @@ function loadBDF(fontInfo) {
351354
var fontCharCode = 0;
352355
var fontBitmap = undefined;
353356
var fontBoundingBox = [0,0,0,0];
354-
var fontChars = [];
357+
var charBoundingBox = [0,0,0,0];
358+
var charAdvance = 0;
355359
var COMMENTS = "", FONTNAME = "";
356360
var glyphs = [];
361+
// https://en.wikipedia.org/wiki/Glyph_Bitmap_Distribution_Format
357362

358363
require("fs").readFileSync(fontInfo.fn).toString().split("\n").forEach((line,lineNo) => {
359-
//console.log(lineNo);
360-
if (line.startsWith("ENCODING")) {
361-
fontCharCode = parseInt(line.substr("ENCODING".length).trim());
362-
//console.log("CODE "+fontCharCode);
363-
fontBoundingBox = [0,0,0,0];
364-
}
364+
// Font stuff
365365
if (line.startsWith("CHARSET_REGISTRY"))
366366
fontCharSet = JSON.parse(line.split(" ")[1].trim());
367367
if (line.startsWith("COPYRIGHT"))
@@ -371,29 +371,53 @@ function loadBDF(fontInfo) {
371371
if (line.startsWith("FONT"))
372372
FONTNAME += "// "+line.substr(4).trim();
373373
if (line.startsWith("FONTBOUNDINGBOX")) {
374-
let box = line.split(/\s+/);
375-
fontInfo.fmWidth = parseInt(box[1]);
376-
fontInfo.height = fontInfo.fmHeight = parseInt(box[2]);
374+
fontBoundingBox = line.split(" ").slice(1).map(x=>parseInt(x));
375+
fontInfo.fmWidth = fontBoundingBox[0];
376+
fontInfo.height = fontInfo.fmHeight = fontBoundingBox[1] - fontBoundingBox[3];
377+
}
378+
// Character stuff
379+
if (line.startsWith("STARTCHAR")) {
380+
fontCharCode = undefined;
381+
charBoundingBox = [0,0,0,0];
382+
charAdvance = 0;
383+
fontBitmap=undefined;
384+
}
385+
if (line.startsWith("ENCODING")) {
386+
fontCharCode = parseInt(line.substr("ENCODING".length).trim());
387+
}
388+
if (line.startsWith("BBX ")) { // per character bounding box
389+
charBoundingBox = line.split(" ").slice(1).map(x=>parseInt(x));
390+
}
391+
if (line.startsWith("DWIDTH ")) { // per character bounding box
392+
charAdvance = parseInt(line.split(" ")[1]);
377393
}
378394
if (line=="ENDCHAR" && fontBitmap) {
379-
var fontChar = String.fromCharCode(fontCharCode);
380395
if (fontBitmap && fontInfo.isChInRange(fontCharCode)) {
396+
// first we need to pad this out
397+
var blankLine = " ".repeat(fontInfo.fmWidth);
398+
var linesBefore = fontBoundingBox[1]-(charBoundingBox[3]+charBoundingBox[1]);
399+
for (var i=0;i<linesBefore;i++)
400+
fontBitmap.unshift(blankLine);
401+
while (fontBitmap.length < fontInfo.fmHeight)
402+
fontBitmap.push(blankLine);
403+
381404
let bmp = fontBitmap; // separate copy for this getGlyph fn
382405
let glyph = fontInfo.getGlyph(fontCharCode, (x,y) => {
383406
if (y<0 || y>=bmp.length) return 0;
384407
return bmp[y][x]=="1" ? 1 : 0;
385408
} );
386-
if (glyph)
387-
glyphs.push(glyph);
409+
if (glyph) {
410+
// glyph.advance = charAdvance; // overwrite calculated advance value with one from file
411+
glyphs.push(glyph);
412+
}
388413
}
389414
fontCharCode = -1;
390415
fontBitmap=undefined;
391416
}
392-
if (line.startsWith("BBX ")) { // per character bounding box
393-
fontBoundingBox = line.split(" ").slice(1).map(x=>parseInt(x));
394-
}
395417
if (fontBitmap!==undefined) {
396418
var l = "";
419+
for (var i=0;i<charBoundingBox[2];i++)
420+
l += " "; // padding
397421
for (var i=0;i<line.length;i++) {
398422
var c = parseInt(line[i],16);
399423
l += ((c+16).toString(2)).substr(-4).replace(/0/g," ");
@@ -424,8 +448,7 @@ function load(fontInfo) {
424448
Font.prototype.debugPixelsUsed = function() {
425449
var pixelsUsedInRow = new Array(this.height);
426450
pixelsUsedInRow.fill(0);
427-
Object.keys(this.glyphs).forEach(ch => {
428-
var glyph = this.glyphs[ch];
451+
this.glyphs.forEach(glyph => {
429452
for (var x=glyph.xStart;x<=glyph.xEnd;x++) {
430453
for (var y=0;y<this.height;y++) {
431454
var col = glyph.getPixel(x,y);
@@ -437,8 +460,8 @@ Font.prototype.debugPixelsUsed = function() {
437460
};
438461

439462
Font.prototype.debugChars = function() {
440-
Object.keys(this.glyphs).forEach(ch => {
441-
this.glyphs[ch].debug();
463+
this.glyphs.forEach(glyph => {
464+
glyph.debug();
442465
console.log();
443466
});
444467
};
@@ -447,8 +470,7 @@ Font.prototype.debugChars = function() {
447470
big filled blocks with the 4 digit char code. This detects these
448471
and removes them */
449472
Font.prototype.removeUnifontPlaceholders = function() {
450-
Object.keys(this.glyphs).forEach(ch => {
451-
let glyph = this.glyphs[ch];
473+
this.glyphs.forEach(glyph => {
452474
if (glyph.xStart==1 && glyph.yStart==1 && glyph.xEnd==15 && glyph.yEnd==14) {
453475
let borderEmpty = true;
454476
let edgesFilled = true;
@@ -473,7 +495,7 @@ Font.prototype.removeUnifontPlaceholders = function() {
473495
if (borderEmpty && edgesFilled) {
474496
// it's a placeholder!
475497
// glyph.debug();
476-
delete this.glyphs[ch]; // remove it
498+
delete this.glyphs[glyph.ch]; // remove it
477499
}
478500
}
479501
});
@@ -484,35 +506,34 @@ Font.prototype.getJS = function(options) {
484506
// options.compressed
485507
options = options||{};
486508
this.glyphPadX = 1;
487-
var charCodes = this.glyphs.map(g=>g.ch).sort((a,b)=>a-b);
509+
var charCodes = this.glyphs.map(g=>g.ch).filter(c=>c!==undefined).sort((a,b)=>a-b);
488510
var charMin = charCodes[0];
511+
var charMax = charCodes[charCodes.length-1];
512+
console.log(`Outputting char range ${charMin}..${charMax}`);
489513
// stats
490514
var minY = this.height;
491515
var maxY = 0;
492516
// get an array of bits
493517
var bits = [];
494518
var charGlyphs = [];
495-
Object.keys(this.glyphs).forEach(ch => {
496-
var glyph = this.glyphs[ch];
519+
var fontWidths = new Array(charMax+1);
520+
fontWidths.fill(0);
521+
this.glyphs.forEach(glyph => {
497522
if (glyph.yEnd > maxY) maxY = glyph.yEnd;
498523
if (glyph.yStart < minY) minY = glyph.yStart;
499-
});
500-
Object.keys(this.glyphs).forEach(ch => {
501-
var glyph = this.glyphs[ch];
502-
glyph.xStart = 0; // all glyphs have to start at 0 now
524+
// all glyphs have go 0...advance-1 now as we have no way to offset
525+
glyph.xStart = 0;
503526
glyph.yStart = 0;
504-
glyph.xEnd = glyph.width-1;
527+
glyph.xEnd = glyph.advance-1;
505528
glyph.yEnd = this.height-1;
529+
glyph.width = glyph.xEnd + 1 - glyph.xStart;
506530
glyph.height = this.height;
507531
glyph.appendBits(bits, {glyphVertical:true});
532+
// create width array - widthBytes
533+
fontWidths[glyph.ch] = glyph.width;
508534
});
509535
// compact array
510536
var fontData = bitsToBytes(bits, this.bpp);
511-
// convert width array - widthBytes
512-
var fontWidths = [];
513-
Object.keys(this.glyphs).forEach(ch => {
514-
fontWidths[ch] = this.glyphs[ch].width;
515-
});
516537
fontWidths = fontWidths.slice(charMin); // don't include chars before we're outputting
517538
var fixedWidth = fontWidths.every(w=>w==fontWidths[0]);
518539

0 commit comments

Comments
 (0)