Created
February 7, 2017 04:03
-
-
Save triplefox/367814ce699f1e556f3b3adcfe595508 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ludamix.bmfont; | |
import haxe.ds.Vector; | |
import ludamix.bmfont.BMFont; | |
using ludamix.MapPair; | |
class BMFontRenderPage<T> { | |
public var data : BMFontPage; | |
public var image : T; | |
public function new(data) {this.data = data;} | |
} | |
class BMFontRenderable<T> { | |
public var page : Array<BMFontRenderPage<T>>; | |
public var font : BMFont; | |
public var kerning : Map<Int, Map<Int,Int>>; | |
public var char : Map<Int, BMFontChar>; | |
public function new(font : BMFont, | |
pagemap : Map<String, BMFontRenderPage<T>>) { | |
this.font = font; | |
this.page = [for (p in font.page) pagemap.get(p.file)]; | |
this.kerning = new Map(); | |
for (k in font.kerning) { | |
this.kerning.setiii(k.first, k.second, k.amount); | |
} | |
this.char = new Map(); | |
for (c in font.char) { | |
this.char.set(c.id, c); | |
} | |
} | |
} | |
/* Holds a state buffer for writing with a BMFont. */ | |
class BMFontWriter<T> { | |
public var MAX_CHARS = 2048; // 72 bytes per character | |
public function new(?max_chars = 2048) { | |
writing = false; | |
this.MAX_CHARS = max_chars; | |
buf = new Vector(MAX_CHARS*8); | |
pg = new Vector(MAX_CHARS); | |
fn = new Vector(MAX_CHARS); | |
scaleW = 1.; | |
scaleH = 1.; | |
} | |
// internal variables | |
public var writing : Bool; // am writing? | |
public var font : Array<BMFontRenderable<T>>; | |
public var last_chr : Int; // last char written | |
public var ox : Float; // origin (cursor) x | |
public var oy : Float; // origin (cursor) y | |
public var bx : Float; // begin x | |
public var by : Float; // begin y | |
public var scaleW : Float; // scale width | |
public var scaleH : Float; // scale height | |
public var maxScaleH : Float; // largest height scale of current line | |
// read variables | |
public var buf : Vector<Float>; // variable vector | |
public var pg : Vector<Int>; // page (per char) | |
public var fn : Vector<Int>; // font (per char) | |
public var curfn : Int; // current font | |
public var left : Float; // extent left | |
public var top : Float; // extent top | |
public var right : Float; // extent right | |
public var bottom : Float; // extent bottom | |
public var len : Int; // number of chars written | |
public var lines : Int; // number of lines written | |
public function begin(font, curfn, x, y, scaleW, scaleH) { | |
if (writing) throw 'writer is still writing'; | |
writing = true; | |
this.font = font; | |
this.curfn = curfn; | |
last_chr = -1; | |
this.ox = x; | |
this.oy = y; | |
this.bx = x; | |
this.by = y; | |
this.lines = 1; | |
// the initial size is "unknown", so we use | |
// values that will definitely be overwritten | |
this.left = x + this.font[curfn].font.common.scaleW; | |
this.top = y + this.font[curfn].font.common.scaleH; | |
this.right = x - this.font[curfn].font.common.scaleW; | |
this.bottom = y - this.font[curfn].font.common.scaleH; | |
this.scaleW = scaleW; | |
this.scaleH = scaleH; | |
this.maxScaleH = scaleH; | |
len = 0; | |
} | |
public inline function resetHoriz() { | |
ox = bx; | |
last_chr = -1; | |
} | |
public inline function resetVert() { | |
oy = by; | |
this.lines = 0; | |
} | |
public inline function lineAdvance() { | |
resetHoriz(); | |
oy += font[curfn].font.common.lineHeight * maxScaleH; | |
maxScaleH = scaleH; | |
this.lines += 1; | |
} | |
public inline function end() { | |
if (!writing) throw 'writer is not writing'; | |
writing = false; | |
} | |
public inline function bufpos(i) {return i << 3;} | |
// source x | |
public inline function sx(i) {return buf[i << 3];} | |
public inline function ssx(i, v) {buf[i << 3] = v;} | |
// source y | |
public inline function sy(i) {return buf[1 + (i << 3)];} | |
public inline function ssy(i, v) {buf[1 + (i << 3)] = v;} | |
// source width | |
public inline function sw(i) {return buf[2 + (i << 3)];} | |
public inline function ssw(i, v) {buf[2 + (i << 3)] = v;} | |
// source height | |
public inline function sh(i) {return buf[3 + (i << 3)];} | |
public inline function ssh(i, v) {buf[3 + (i << 3)] = v;} | |
// dest x | |
public inline function dx(i) {return buf[4 + (i << 3)];} | |
public inline function sdx(i, v) {buf[4 + (i << 3)] = v;} | |
// dest y | |
public inline function dy(i) {return buf[5 + (i << 3)];} | |
public inline function sdy(i, v) {buf[5 + (i << 3)] = v;} | |
// dest width | |
public inline function dw(i) {return buf[6 + (i << 3)];} | |
public inline function sdw(i, v) {buf[6 + (i << 3)] = v;} | |
// dest height | |
public inline function dh(i) {return buf[7 + (i << 3)];} | |
public inline function sdh(i, v) {buf[7 + (i << 3)] = v;} | |
public inline function width() {return right - left;} | |
public inline function height() {return bottom - top;} | |
public inline function write(ch : Int) { | |
if (!writing) throw 'writer is not writing'; | |
var chd = font[curfn].char.get(ch); | |
if (chd != null && len < MAX_CHARS) { | |
// add kerning | |
ox += font[curfn].kerning.getiii(last_chr, ch, 0) * scaleW; | |
// set values to new character | |
var bi = len << 3; | |
var destx = ox + chd.xoffset * scaleW; | |
var desty = oy + chd.yoffset * scaleH; | |
var destw = chd.width * scaleW; | |
var desth = chd.height * scaleH; | |
buf[bi] = chd.x; | |
buf[bi+1] = chd.y; | |
buf[bi+2] = chd.width; | |
buf[bi+3] = chd.height; | |
buf[bi+4] = destx; | |
buf[bi+5] = desty; | |
buf[bi+6] = destw; | |
buf[bi+7] = desth; | |
pg[len] = chd.page; | |
fn[len] = curfn; | |
// calc extents | |
if (destx < left) left = destx; | |
if (destx + destw > right) right = destx + destw; | |
if (desty < top) top = desty; | |
if (desty + desth > bottom) bottom = desty + desth; | |
// advance | |
last_chr = ch; | |
ox += chd.xadvance * scaleW; | |
len += 1; | |
if (maxScaleH < scaleH) maxScaleH = scaleH; | |
} | |
} | |
/* automatically break a line into word and linebreak tokens. */ | |
public static function breakLine(s : String, | |
keep_existing_breaks : Bool) { | |
var d0 = new Array<WordWrapData>(); | |
var sa : Array<String>; | |
if (keep_existing_breaks) { | |
for (n in s.split("\n")) { | |
var tok = ""; | |
for (idx in 0...n.length) { | |
var c0 = n.charAt(idx); | |
if (c0==" ") | |
{ | |
if (tok.length > 0) { | |
d0.push(WWToken(tok)); | |
tok = ""; | |
} | |
d0.push(WWWhitespace); | |
} else { | |
tok += c0; | |
} | |
} | |
if (tok.length > 0) { | |
d0.push(WWToken(tok)); | |
} | |
d0.push(WWBreak); | |
} | |
} else { | |
s = StringTools.replace(s, "\n", " "); | |
var tok = ""; | |
for (idx in 0...s.length) { | |
var c0 = s.charAt(idx); | |
if (c0==" ") | |
{ | |
if (tok.length > 0) { | |
d0.push(WWToken(tok)); | |
tok = ""; | |
} | |
d0.push(WWWhitespace); | |
} else { | |
tok += c0; | |
} | |
} | |
if (tok.length > 0) { | |
d0.push(WWToken(tok)); | |
} | |
} | |
return d0; | |
} | |
private inline function expand_rect(a : {top:Float,left:Float,bottom:Float,right:Float}) { | |
if (a.left > left) a.left = left; | |
if (a.right < right) a.right = right; | |
if (a.top > top) a.top = top; | |
if (a.bottom < bottom) a.bottom = bottom; | |
} | |
private inline function reset_origin() { | |
left = ox; | |
right = ox; | |
top = oy; | |
bottom = oy; | |
} | |
private inline function reset_rect(a : {top:Float,left:Float,bottom:Float,right:Float}) { | |
left = a.left; | |
right = a.right; | |
top = a.top; | |
bottom = a.bottom; | |
} | |
/* render a breakLine()'d array with word wrapping. */ | |
public function wrap(s : Array<WordWrapData>, lwidth : Float) { | |
var cw = 0.; | |
var idx = 0; | |
reset_origin(); | |
var rect = {top:top,left:left,bottom:bottom,right:right}; | |
while (idx < s.length) { | |
if (this.width() > lwidth) | |
throw '$idx of $s'; | |
var n = s[idx]; | |
var line_start = ox == bx; | |
var preMaxScaleH = maxScaleH; | |
switch(n) { | |
case WWBreak: | |
lineAdvance(); | |
case WWToken(v): | |
var c = 0; | |
var word_rect = {top:rect.top,left:rect.left,bottom:rect.bottom,right:rect.right}; | |
while(c < v.length) { | |
write(v.charCodeAt(c)); | |
if (left < bx) { // rect is going to expand left: back up and offset | |
var offset = bx-left; | |
len -= 1; | |
reset_rect(rect); | |
resetHoriz(); | |
maxScaleH = preMaxScaleH; | |
ox += offset; | |
write(v.charCodeAt(c)); | |
} | |
if (this.width() > lwidth) { // back up this long word | |
if (line_start) { // go back one char, break up the word, write a char | |
len -= 1; | |
maxScaleH = preMaxScaleH; | |
reset_rect(rect); | |
lineAdvance(); | |
write(v.charCodeAt(c)); | |
c += 1; | |
expand_rect(rect); | |
} else { // go back to the start of the word, new line | |
len -= c + 1; | |
maxScaleH = preMaxScaleH; | |
c = 0; | |
line_start = true; | |
rect.top = word_rect.top; | |
rect.left = word_rect.left; | |
rect.bottom = word_rect.bottom; | |
rect.right = word_rect.right; | |
reset_rect(rect); | |
lineAdvance(); | |
expand_rect(rect); | |
} | |
} else { | |
c += 1; | |
expand_rect(rect); | |
} | |
} | |
case WWWhitespace: | |
// this is presently implemented as a copypaste of token with a single character, | |
// plus special-casing for the line start and overflow. | |
var v = line_start ? "" : " "; | |
var c = 0; | |
var word_rect = {top:rect.top,left:rect.left,bottom:rect.bottom,right:rect.right}; | |
while(c < v.length) { | |
write(v.charCodeAt(c)); | |
if (left < bx) { // rect is going to expand left: back up and offset | |
var offset = bx-left; | |
len -= 1; | |
reset_rect(rect); | |
resetHoriz(); | |
maxScaleH = preMaxScaleH; | |
ox += offset; | |
write(v.charCodeAt(c)); | |
} | |
if (this.width() > lwidth) { // back up this long word | |
if (line_start) { // go back one char, break up the word, write a char | |
len -= 1; | |
maxScaleH = preMaxScaleH; | |
reset_rect(rect); | |
lineAdvance(); | |
write(v.charCodeAt(c)); | |
c += 1; | |
expand_rect(rect); | |
} else { // back up and ignore the space | |
len -= 1; | |
maxScaleH = preMaxScaleH; | |
reset_rect(rect); | |
c += 1; | |
} | |
} else { | |
c += 1; | |
expand_rect(rect); | |
} | |
} | |
} | |
idx += 1; | |
} | |
reset_rect(rect); | |
} | |
public function translateTopLeft(x : Float, y : Float) { | |
var xo = x - this.left; | |
var yo = y - this.top; | |
this.left += xo; | |
this.right += xo; | |
this.top += yo; | |
this.bottom += yo; | |
ox += xo; | |
oy += yo; | |
bx += xo; | |
by += yo; | |
for (i0 in 0...len) { | |
var bi = i0 << 3; | |
buf[bi+4] += xo; | |
buf[bi+5] += yo; | |
} | |
} | |
/* centers around the number of lines, line height, and baseline, | |
maintains vertical consistency across multiple bodies of text */ | |
public function translateBodyCenter(x, y) { | |
translateTopLeft(x - this.width()/2, | |
y + (font[curfn].font.common.lineHeight - font[curfn].font.common.base)/2 | |
- (this.lines * font[curfn].font.common.lineHeight)/2); | |
} | |
/* centers around the displayed content which may be a little less | |
than line height */ | |
public function translateDisplayCenter(x, y) { | |
translateTopLeft(x - this.width()/2, y - this.height()/2); | |
} | |
} | |
enum WordWrapData { | |
WWToken(s : String); | |
WWBreak; | |
WWWhitespace; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment