Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 48 additions & 5 deletions h2d/Font.hx
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,18 @@ class Font {
Defaults to `hxd.Charset.getDefault()`.
**/
public var charset : hxd.Charset;
/**
Optional fallback font used when a character glyph is not found in this font.
**/
public var fallback : Null<Font>;
/**
List of fallback fonts used when a character is not found in this font or fallback.
**/
public var fallbacks : Array<Font>;
/**
List of tiles if font is split across multiple texture pages.
**/
public var tiles : Array<h2d.Tile>;
var glyphs : Map<Int,FontChar>;
var nullChar : FontChar;
var defaultChar : FontChar;
Expand Down Expand Up @@ -216,13 +228,31 @@ class Font {
/**
Returns a `FontChar` instance corresponding to the `code`.
If font char is not present in glyph list, `charset.resolveChar` is called.
If still not found, queries fallback and fallbacks fonts.
Returns `null` if glyph under specified charcode does not exist.
@param code The charcode to search for.
**/
public inline function getChar( code : Int ) {
public function getChar( code : Int ) {
var c = glyphs.get(code);
if( c == null ) {
c = charset.resolveChar(code, glyphs);
if( c == null ) {
if( fallback != null ) {
var fc = fallback.getChar(code);
if( fc != null && fc != fallback.defaultChar && fc != fallback.nullChar )
c = fc;
}
if( c == null && fallbacks != null ) {
for( f in fallbacks ) {
if( f == null ) continue;
var fc = f.getChar(code);
if( fc != null && fc != f.defaultChar && fc != f.nullChar ) {
c = fc;
break;
}
}
}
}
if( c == null )
c = code == "\r".code || code == "\n".code ? nullChar : defaultChar;
}
Expand Down Expand Up @@ -260,6 +290,9 @@ class Font {
f.type = type;
f.offsetX = offsetX;
f.offsetY = offsetY;
f.fallback = fallback;
if( fallbacks != null ) f.fallbacks = fallbacks.copy();
if( tiles != null ) f.tiles = [for( t in tiles ) t.clone()];
for( g in glyphs.keys() ) {
var c = glyphs.get(g);
var c2 = c.clone();
Expand Down Expand Up @@ -297,19 +330,29 @@ class Font {
}

/**
Checks if character is present in glyph list.
Checks if character is present in glyph list or any fallback font.
Compared to `getChar` does not check if it exists through `Font.charset`.
@param code The charcode to look up.
**/
public function hasChar( code : Int ) : Bool {
return glyphs.get(code) != null;
if( glyphs.get(code) != null ) return true;
if( fallback != null && fallback.hasChar(code) ) return true;
if( fallbacks != null ) {
for( f in fallbacks )
if( f != null && f.hasChar(code) ) return true;
}
return false;
}

/**
Disposes of the Font instance. Equivalent to `Tile.dispose`.
Disposes of the Font instance and its tiles.
**/
public function dispose() {
tile.dispose();
if( tile != null ) tile.dispose();
if( tiles != null ) {
for( t in tiles )
if( t != null && t != tile ) t.dispose();
}
}

/**
Expand Down
5 changes: 3 additions & 2 deletions h2d/Text.hx
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,8 @@ class Text extends Drawable {
var newline = cc == '\n'.code;
var esize = e.width + e.getKerningOffset(prevChar);
var isComplement = (i < text.length - 1 && font.charset.isComplementChar(StringTools.fastCodeAt(text, i + 1)));
if( font.charset.isBreakChar(cc) && !isComplement ) {
var isLeading = font.charset.isLeadingChar(cc);
if( font.charset.isBreakChar(cc) && !isComplement && !isLeading ) {
if( lines.length == 0 && leftMargin > 0 && x > maxWidth ) {
lines.push("");
if ( sizes != null ) sizes.push(leftMargin);
Expand All @@ -356,7 +357,7 @@ class Text extends Drawable {
if ( k >= text.length )
break;
var nc = StringTools.fastCodeAt(text, k);
if ( !font.charset.isComplementChar(nc) ) break;
if ( !font.charset.isComplementChar(nc) && !font.charset.isLeadingChar(cc) ) break;
}
}
if( lineBreak && (size > maxWidth || (!breakFound && size + afterData > maxWidth)) ) {
Expand Down
4 changes: 4 additions & 0 deletions h3d/prim/ModelDatabase.hx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class ModelDatabase {
}

public function getDefaultLodConfig( dir : String ) : Array<Float> {
#if (sys || nodejs)
var fs = Std.downcast(hxd.res.Loader.currentInstance.fs, hxd.fs.LocalFileSystem);
if (fs == null)
return baseLodConfig;
Expand All @@ -103,6 +104,9 @@ class ModelDatabase {
});

return c;
#else
return baseLodConfig;
#end
}

public static var current = new ModelDatabase();
Expand Down
11 changes: 10 additions & 1 deletion hxd/Charset.hx
Original file line number Diff line number Diff line change
Expand Up @@ -140,14 +140,23 @@ class Charset {
}

static var complementChars : Map<Int,Bool> = {
var str = "ヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻。,";
var str = "ヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々㻉。,、!?:;”’)》】〉』〗〕…—~·!?:;)]}.,/\";
[for( i in 0...str.length ) str.charCodeAt(i) => true];
}

static var leadingChars : Map<Int,Bool> = {
var str = "“‘(《【〈『〖〔([{";
[for( i in 0...str.length ) str.charCodeAt(i) => true];
}

public function isComplementChar(code) {
return complementChars.exists(code);
}

public function isLeadingChar(code) {
return leadingChars.exists(code);
}

static var inst : Charset;
public static function getDefault() {
if( inst == null ) inst = new Charset();
Expand Down
94 changes: 67 additions & 27 deletions hxd/fmt/bfnt/FontParser.hx
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,33 @@ class FontParser {
// TODO: Support multiple textures per font.

var tile : h2d.Tile = null;
var pages : Array<h2d.Tile> = [];
var font : h2d.Font = new h2d.Font(null, 0);
var glyphs = font.glyphs;

inline function resolveTileSameName() {
inline function resolveTileSameName() : h2d.Tile {
font.tilePath = new haxe.io.Path(path).file + ".png";
tile = resolveTile(haxe.io.Path.withExtension(path, "png"));
return tile;
}

inline function resolveTileWithFallback( tilePath : String ) {
inline function resolveTileWithFallback( tilePath : String ) : h2d.Tile {
var t : h2d.Tile = null;
try {
font.tilePath = tilePath;
tile = resolveTile(haxe.io.Path.join([haxe.io.Path.directory(path), tilePath]));
t = resolveTile(haxe.io.Path.join([haxe.io.Path.directory(path), tilePath]));
} catch ( e : Dynamic ) {
trace('Warning: Could not find referenced font texture at "${tilePath}", trying to resolve same name as fnt!');
resolveTileSameName();
t = resolveTileSameName();
}
if ( tile == null ) tile = t;
return t;
}

inline function getPageTile( pageId : Int ) : h2d.Tile {
if ( pages != null && pageId >= 0 && pageId < pages.length && pages[pageId] != null )
return pages[pageId];
return tile;
}

// Supported formats:
Expand All @@ -42,7 +53,7 @@ class FontParser {

switch( bytes.getInt32(0) ) {
case 0x544E4642: // Internal BFNT
return hxd.fmt.bfnt.Reader.parse(bytes, function( tp : String ) { resolveTileWithFallback(tp); return tile; });
return hxd.fmt.bfnt.Reader.parse(bytes, function( tp : String ) { return resolveTileWithFallback(tp); });

case 0x6D783F3C, // <?xml : XML file
0x6E6F663C: // <font>
Expand All @@ -56,24 +67,29 @@ class FontParser {
font.baseLine = Std.parseInt(xml.node.common.att.base);

for ( p in xml.node.pages.elements ) {
if ( p.att.id == "0" ) {
resolveTileWithFallback(p.att.file);
} else {
trace("Warning: BMF format only supports one page at the moment.");
}
var pid = Std.parseInt(p.att.id);
var pt = resolveTileWithFallback(p.att.file);
pages[pid] = pt;
if ( pid == 0 || tile == null ) tile = pt;
}

var chars = xml.node.chars.elements;
for( c in chars) {
var t = tile.sub(Std.parseInt(c.att.x), Std.parseInt(c.att.y), Std.parseInt(c.att.width), Std.parseInt(c.att.height), Std.parseInt(c.att.xoffset), Std.parseInt(c.att.yoffset));
var pageId = c.has.page ? Std.parseInt(c.att.page) : 0;
var curTile = getPageTile(pageId);
var t = curTile.sub(Std.parseInt(c.att.x), Std.parseInt(c.att.y), Std.parseInt(c.att.width), Std.parseInt(c.att.height), Std.parseInt(c.att.xoffset), Std.parseInt(c.att.yoffset));
var fc = new h2d.Font.FontChar(t, Std.parseInt(c.att.xadvance));
var kerns = xml.node.kernings.elements;
for (k in kerns)
if (k.att.second == c.att.id)
fc.addKerning(Std.parseInt(k.att.first), Std.parseInt(k.att.amount));

glyphs.set(Std.parseInt(c.att.id), fc);
}

if ( xml.hasNode.kernings ) {
for (k in xml.node.kernings.elements) {
var second = Std.parseInt(k.att.second);
var fc = glyphs.get(second);
if (fc != null)
fc.addKerning(Std.parseInt(k.att.first), Std.parseInt(k.att.amount));
}
}
} else {
// support for the FontBuilder/Divo format
resolveTileSameName();
Expand Down Expand Up @@ -150,19 +166,23 @@ class FontParser {
case "base": font.baseLine = extractInt();
case "pages":
pageCount = extractInt();
if (pageCount != 1) trace("Warning: BMF format only supports one page at the moment.");
}
next();
}
case "page":
var pageId = 0;
while (idx < line.length && reg.matchSub(line, idx)) {
switch (reg.matched(1)) {
case "file": resolveTileWithFallback(processValue());
case "id": pageId = extractInt();
case "file":
var pt = resolveTileWithFallback(processValue());
pages[pageId] = pt;
if ( pageId == 0 || tile == null ) tile = pt;
}
next();
}
case "char":
var id = 0, x = 0, y = 0, width = 0, height = 0, xoffset = 0, yoffset = 0, xadvance = 0;
var id = 0, x = 0, y = 0, width = 0, height = 0, xoffset = 0, yoffset = 0, xadvance = 0, page = 0;
while (idx < line.length && reg.matchSub(line, idx)) {
switch (reg.matched(1)) {
case "id": id = extractInt();
Expand All @@ -173,10 +193,12 @@ class FontParser {
case "xoffset": xoffset = extractInt();
case "yoffset": yoffset = extractInt();
case "xadvance": xadvance = extractInt();
case "page": page = extractInt();
}
next();
}
var t = tile.sub(x, y, width, height, xoffset, yoffset);
var curTile = getPageTile(page);
var t = curTile.sub(x, y, width, height, xoffset, yoffset);
var fc = new h2d.Font.FontChar(t, xadvance);
glyphs.set(id, fc);
case "kerning":
Expand Down Expand Up @@ -217,19 +239,33 @@ class FontParser {
// skip scaleW (2), scaleH (2)
bytes.position += 4;
pageCount = bytes.readUInt16();
if (pageCount != 1) trace("Warning: BMF format only supports one page at the moment.");
// skip bitField (1), channels (4)
case 3: // pages
var name : String = bytes.readUntil(0);
resolveTileWithFallback(name);
while ( bytes.position < pos + length ) {
var name : String = bytes.readUntil(0);
if ( name.length > 0 ) {
var pt = resolveTileWithFallback(name);
pages.push(pt);
if ( tile == null ) tile = pt;
}
}
case 4: // chars
var count : Int = Std.int(length / 20);
while ( count > 0 ) {
var cid = bytes.readInt32();
var t = tile.sub(bytes.readUInt16(), bytes.readUInt16(), bytes.readUInt16(), bytes.readUInt16(), bytes.readInt16(), bytes.readInt16());
var fc = new h2d.Font.FontChar(t, bytes.readInt16());
var cx = bytes.readUInt16();
var cy = bytes.readUInt16();
var cw = bytes.readUInt16();
var ch = bytes.readUInt16();
var cdx = bytes.readInt16();
var cdy = bytes.readInt16();
var cadv = bytes.readInt16();
var pageId = bytes.readByte();
var chnl = bytes.readByte();
var curTile = getPageTile(pageId);
var t = curTile.sub(cx, cy, cw, ch, cdx, cdy);
var fc = new h2d.Font.FontChar(t, cadv);
glyphs.set(cid, fc);
bytes.position += 2; // skip page and channel
count--;
}
case 5: // kerning
Expand All @@ -251,8 +287,12 @@ class FontParser {
throw "Unknown font signature " + StringTools.hex(sign, 8);
}
if( glyphs.get(" ".code) == null )
glyphs.set(" ".code, new h2d.Font.FontChar(tile.sub(0, 0, 0, 0), font.size>>1));
glyphs.set(" ".code, new h2d.Font.FontChar((tile != null ? tile : h2d.Tile.fromColor(0, 0, 0, 0)).sub(0, 0, 0, 0), font.size>>1));

if ( pages.length > 0 ) {
font.tiles = pages;
if ( tile == null ) tile = pages[0];
}
font.tile = tile;

if( font.baseLine == 0 )
Expand Down
Loading