Skip to content

Instantly share code, notes, and snippets.

@postcasio
Created September 14, 2019 22:31
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save postcasio/6e12f94bc1b16bd4fb19d07664ace2ba to your computer and use it in GitHub Desktop.
Save postcasio/6e12f94bc1b16bd4fb19d07664ace2ba to your computer and use it in GitHub Desktop.
BitmapFont
export default class BitmapFont {
fileName: string;
height: number;
constructor(path: string);
drawText(surface: Surface, x: number, y: number, text: string, color?: Color, wrap_width?: number): void;
getTextSize(text: string, wrap?: number): {
width: number;
height: number;
};
heightOf(text: string, wrap?: number): number;
widthOf(text: string): number;
wordWrap(text: string, wrapWidth: number): string[];
}
var parseBmfontAscii = function parseBMFontAscii(data) {
if (!data)
throw new Error('no data provided')
data = data.toString().trim();
var output = {
pages: [],
chars: [],
kernings: []
};
var lines = data.split(/\r\n?|\n/g);
if (lines.length === 0)
throw new Error('no data in BMFont file')
for (var i = 0; i < lines.length; i++) {
var lineData = splitLine(lines[i], i);
if (!lineData) //skip empty lines
continue
if (lineData.key === 'page') {
if (typeof lineData.data.id !== 'number')
throw new Error('malformed file at line ' + i + ' -- needs page id=N')
if (typeof lineData.data.file !== 'string')
throw new Error('malformed file at line ' + i + ' -- needs page file="path"')
output.pages[lineData.data.id] = lineData.data.file;
} else if (lineData.key === 'chars' || lineData.key === 'kernings') ; else if (lineData.key === 'char') {
output.chars.push(lineData.data);
} else if (lineData.key === 'kerning') {
output.kernings.push(lineData.data);
} else {
output[lineData.key] = lineData.data;
}
}
return output
};
function splitLine(line, idx) {
line = line.replace(/\t+/g, ' ').trim();
if (!line)
return null
var space = line.indexOf(' ');
if (space === -1)
throw new Error("no named row at line " + idx)
var key = line.substring(0, space);
line = line.substring(space + 1);
//clear "letter" field as it is non-standard and
//requires additional complexity to parse " / = symbols
line = line.replace(/letter=[\'\"]\S+[\'\"]/gi, '');
line = line.split("=");
line = line.map(function(str) {
return str.trim().match((/(".*?"|[^"\s]+)+(?=\s*|\s*$)/g))
});
var data = [];
for (var i = 0; i < line.length; i++) {
var dt = line[i];
if (i === 0) {
data.push({
key: dt[0],
data: ""
});
} else if (i === line.length - 1) {
data[data.length - 1].data = parseData(dt[0]);
} else {
data[data.length - 1].data = parseData(dt[0]);
data.push({
key: dt[1],
data: ""
});
}
}
var out = {
key: key,
data: {}
};
data.forEach(function(v) {
out.data[v.key] = v.data;
});
return out
}
function parseData(data) {
if (!data || data.length === 0)
return ""
if (data.indexOf('"') === 0 || data.indexOf("'") === 0)
return data.substring(1, data.length - 1)
if (data.indexOf(',') !== -1)
return parseIntList(data)
return parseInt(data, 10)
}
function parseIntList(data) {
return data.split(',').map(function(val) {
return parseInt(val, 10)
})
}
function createTriStripList(w, h, x, y, mask = Color.White) {
return new VertexList([
{ x: 0, y: 0, u: x, v: 1 - y, color: mask },
{ x: 1, y: 0, u: x + w, v: 1 - y, color: mask },
{ x: 0, y: 1, u: x, v: 1 - (y + h), color: mask },
{ x: 1, y: 1, u: x + w, v: 1 - (y + h), color: mask }
]);
}
function createShape(texture, w, h, x, y, mask = Color.White) {
return new Shape(ShapeType.TriStrip, texture, createTriStripList(w / texture.width, h / texture.height, x / texture.width, y / texture.height, mask));
}
class BitmapFont {
constructor(path) {
this.characters = {};
const data = parseBmfontAscii(FS.readFile(path));
this.fileName = path;
const dir = FS.directoryOf(path);
this.textures = data.pages.map(page => new Texture(dir + "/" + page));
this.height = data.common.lineHeight;
this.base = data.common.base;
this.characters = data.chars.reduce((chars, char) => {
chars[char.id] = Object.assign(Object.assign({}, char), { texture: this.textures[char.page], shape: createShape(this.textures[char.page], char.width, char.height, char.x, char.y) });
return chars;
}, {});
}
drawText(surface, x, y, text, color, wrap_width) {
let currentX = x;
let currentY = y;
const transform = new Transform();
for (let i = 0; i < text.length; i++) {
let charCode = text.charCodeAt(i);
if (charCode === 10) {
currentX = x;
currentY += this.height;
continue;
}
const char = this.characters[charCode];
if (!char) {
continue;
}
if (char.id >= 33) {
transform.identity();
transform.scale(char.width, char.height);
transform.translate(currentX + char.xoffset, currentY + char.yoffset + this.height / this.base);
char.shape.draw(surface, transform);
}
currentX += char.xadvance;
if (wrap_width && currentX >= wrap_width + x) {
currentX = x;
currentY += this.height;
}
}
}
getTextSize(text, wrap) {
let currentX = 0;
let currentY = 0;
let maxX = 0;
for (let i = 0; i < text.length; i++) {
let charCode = text.charCodeAt(i);
if (charCode === 10) {
maxX = Math.max(currentX, maxX);
currentX = 0;
currentY += this.height;
continue;
}
const char = this.characters[charCode];
if (!char) {
continue;
}
currentX += char.xadvance;
if (wrap && currentX >= wrap) {
maxX = Math.max(currentX, maxX);
currentX = 0;
currentY += this.height;
}
}
return { width: Math.max(currentX, maxX), height: currentY + this.height };
}
heightOf(text, wrap) {
return wrap ? this.getTextSize(text, wrap).height : this.height;
}
widthOf(text) {
return this.getTextSize(text).width;
}
wordWrap(text, wrapWidth) {
let currentX = 0;
let currentLine = "";
const lines = [];
for (let i = 0; i < text.length; i++) {
let charCode = text.charCodeAt(i);
if (charCode === 10) {
lines.push(currentLine);
currentLine = "";
continue;
}
const char = this.characters[charCode];
if (!char) {
continue;
}
currentLine += text.substr(i, 1);
if (currentX >= wrapWidth) {
lines.push(currentLine);
currentLine = "";
}
}
lines.push(currentLine);
return lines;
}
}
export default BitmapFont;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment