X-Git-Url: http://nitlanguage.org diff --git a/lib/gamnit/bmfont.nit b/lib/gamnit/bmfont.nit index 4abdaa5..d052922 100644 --- a/lib/gamnit/bmfont.nit +++ b/lib/gamnit/bmfont.nit @@ -24,7 +24,7 @@ module bmfont private import dom -import font +intrude import font # BMFont description, parsed with `Text::parse_bmfont` or loaded as a `BMFontAsset` # @@ -40,7 +40,7 @@ class BMFont var face: Text # Size of the source true type font - var size: Int + var size: Float # Is the font bold? var bold: Bool @@ -67,16 +67,16 @@ class BMFont # Information common to all characters # Distance in pixels between each line of text - var line_height: Int + var line_height: Float # Pixels from the top of the line to the base of the characters - var base: Int + var base: Float # Width of the texture - var scale_w: Int + var scale_w: Float # Height of the texture - var scale_h: Int + var scale_h: Float # Textures var pages = new Map[String, TextureAsset] @@ -85,7 +85,14 @@ class BMFont var chars = new Map[Char, BMFontChar] # Distance between certain characters - var kernings = new HashMap2[Char, Char, Int] + var kernings = new HashMap2[Char, Char, Float] + + # Additional distance between `prev_char` and `char` + fun kerning(prev_char: nullable Char, char: Char): Float + do + if prev_char == null then return 0.0 + return kernings[prev_char, char] or else 0.0 + end redef fun to_s do return "<{class_name} {face} at {size} pt, "+ "{pages.length} pages, {chars.length} chars>" @@ -111,34 +118,37 @@ end class BMFontChar # Subtexture left coordinate - var x: Int + var x: Float # Subtexture top coordinate - var y: Int + var y: Float # Subtexture width - var width: Int + var width: Float # Subtexture height - var height: Int + var height: Float # Drawing offset on X - var xoffset: Int + var xoffset: Float # Drawing offset on Y - var yoffset: Int + var yoffset: Float # Cursor advance after drawing this character - var xadvance: Int + var xadvance: Float # Full texture contaning this character and others - var page: TextureAsset + var page: RootTexture # TODO Channel where the image is found #var chnl: Int # Subtexture with this character image only - var subtexture: Texture = page.subtexture(x, y, width, height) is lazy + var subtexture: Texture = page.subtexture(x, y, width, height) is lazy, writable + + # Scale to apply to this char only + var scale = 1.0 is writable end redef class Text @@ -174,10 +184,10 @@ redef class Text # """ # # var fnt = desc.parse_bmfont("dir_in_assets").value - # assert fnt.to_s == "" - # assert fnt.line_height == 80 - # assert fnt.kernings['A', 'C'] == -1 - # assert fnt.chars['A'].page.path == "dir_in_assets/arial.png" + # assert fnt.to_s == "" + # assert fnt.line_height == 80.0 + # assert fnt.kernings['A', 'C'] == -1.0 + # assert fnt.chars['A'].page.as(TextureAsset).path == "dir_in_assets/arial.png" # ~~~ fun parse_bmfont(dir: String): MaybeError[BMFont, Error] do @@ -208,16 +218,16 @@ redef class Text var fnt = new BMFont( info_map["face"], - info_map["size"].to_i, + info_map["size"].to_f, info_map["bold"] == "1", info_map["italic"] == "1", info_map["unicode"] == "1", info_map["padding"], info_map["spacing"], - common_map["lineHeight"].to_i, - common_map["base"].to_i, - common_map["scaleW"].to_i, - common_map["scaleH"].to_i + common_map["lineHeight"].to_f, + common_map["base"].to_f, + common_map["scaleW"].to_f, + common_map["scaleH"].to_f ) # Pages / pixel data files @@ -238,10 +248,10 @@ redef class Text var id = attributes["id"].to_i.code_point var c = new BMFontChar( - attributes["x"].to_i, attributes["y"].to_i, - attributes["width"].to_i, attributes["height"].to_i, - attributes["xoffset"].to_i, attributes["yoffset"].to_i, - attributes["xadvance"].to_i, + attributes["x"].to_f, attributes["y"].to_f, + attributes["width"].to_f, attributes["height"].to_f, + attributes["xoffset"].to_f, attributes["yoffset"].to_f, + attributes["xadvance"].to_f, fnt.pages[attributes["page"]]) fnt.chars[id] = c @@ -256,7 +266,7 @@ redef class Text var attributes = item.attributes_to_map var first = attributes["first"].to_i.code_point var second = attributes["second"].to_i.code_point - var amount = attributes["amount"].to_i + var amount = attributes["amount"].to_f fnt.kernings[first, second] = amount end end @@ -270,9 +280,10 @@ end # ~~~ # redef class App # var font = new BMFontAsset("arial.fnt") -# var ui_text = new TextSprites(font, ui_camera.top_left) +# var pos: Point3d[Float] = ui_camera.top_left.offset(128.0, -128.0, 0.0) +# var ui_text = new TextSprites(font, pos) # -# redef fun on_create +# redef fun create_scene # do # super # @@ -354,35 +365,158 @@ class BMFontAsset do var dx = 0.0 var dy = 0.0 + var text_width = 0.0 + var line_sprites = new Array[Sprite] + var height = 0.0 - var line_height = desc.line_height.to_f + # Has the current line height been added to `height`? + var line_height_counted = false - var prev_char = null - for c in text do + # TextSprite customization + var max_width = text_sprites.max_width + var max_height = text_sprites.max_height + var scale = text_sprites.scale + + # Font customization + var line_height = desc.line_height * scale + var partial_line_skip = line_height * partial_line_mod.to_f + + # Links data + text_sprites.links.clear + var in_link = false + var link_sprites = new Array[Sprite] + var link_name = "" - var partial_line_mod = 0.4 + # Loop over all characters + var prev_char = null + var i = -1 + while i < text.length - 1 do + i += 1 + var c = text[i] # Special characters + var word_break = false if c == '\n' then - dy -= line_height.to_f - dx = 0.0 #advance/2.0 + justify(line_sprites, text_sprites.align, dx) + dy -= line_height + if max_height != null and max_height < -dy + line_height then break + dx = 0.0 + if not line_height_counted then + # Force to account for empty lines + height += line_height + end + line_height_counted = false prev_char = null continue else if c == pld then - dy -= line_height * partial_line_mod.to_f - prev_char = null + dy -= partial_line_skip + height += partial_line_skip + word_break = true continue else if c == plu then - dy += line_height * partial_line_mod.to_f - prev_char = null + dy += partial_line_skip + height -= partial_line_skip # We could keep two heights and return the max + word_break = true continue else if c.is_whitespace then - var advance = if desc.chars.keys.has(' ') then - desc.chars[' '].xadvance.to_f + var space_advance = if desc.chars.keys.has(' ') then + desc.chars[' '].xadvance else if desc.chars.keys.has('f') then - desc.chars['f'].xadvance.to_f + desc.chars['f'].xadvance else 16.0 - dx += advance + dx += space_advance * scale + word_break = true + else if c == '[' then + # Open link? + if i + 1 < text.length and text[i+1] == '[' then + # Escape if duplicated + i += 1 + else + in_link = true + continue + end + else if c == ']' then + # Close link? + if i + 1 < text.length and text[i+1] == ']' then + # Escape if duplicated + i += 1 + else + # If there's a () use it as link_name + var j = i + 1 + if j < text.length and text[j] == '(' then + var new_name + new_name = "" + loop + j += 1 + if j > text.length then + # No closing ), abort + new_name = null + break + end + + var l = text[j] + if l == ')' then break + new_name += l.to_s + end + if new_name != null then + link_name = new_name + i = j + end + end + + # Register the link for the clients + text_sprites.links[link_name] = link_sprites + + # Prepare next link + in_link = false + link_sprites = new Array[Sprite] + link_name = "" + continue + end + end + + if in_link then link_name += c.to_s + + # End of a word? + if word_break then + # If we care about line width, check for overflow + if max_width != null then + # Calculate the length of the next word + var prev_w = null + var word_len = 0.0 + for wi in [i+1..text.length[ do + var w = text[wi] + + if w == '\n' or w == pld or w == plu or w.is_whitespace or (in_link and w == ']') then break + + if not desc.chars.keys.has(w) then + var rc = replacement_char + if rc == null then continue + w = rc + end + + word_len += advance(prev_w, w) * scale + prev_w = w + end + + # Would the line be too long? + if dx + word_len > max_width then + justify(line_sprites, text_sprites.align, dx) + dy -= line_height + if max_height != null and max_height < -dy + line_height then break + dx = 0.0 + line_height_counted = false + + if not text_sprites.wrap then + # Cut short, skip everything until the next new line + while c != '\n' and i < text.length - 1 do + i += 1 + c = text[i] + end + end + end + end + prev_char = null continue end @@ -395,28 +529,61 @@ class BMFontAsset end var char_info = desc.chars[c] - var advance = char_info.xadvance.to_f - - var kerning = 0.0 - if prev_char != null then - kerning = (desc.kernings[prev_char, c] or else 0).to_f - end + var advance = char_info.xadvance + var kerning = desc.kerning(prev_char, c) - var x = dx + char_info.width.to_f/2.0 + char_info.xoffset.to_f + kerning - var y = dy - char_info.height.to_f/2.0 - char_info.yoffset.to_f + var x = dx + (char_info.width/2.0 + char_info.xoffset + kerning) * scale + var y = dy - (char_info.height/2.0 + char_info.yoffset) * scale var pos = text_sprites.anchor.offset(x, y, 0.0) - text_sprites.sprites.add new Sprite(char_info.subtexture, pos) + var s = new Sprite(char_info.subtexture, pos) + s.scale = scale * char_info.scale + text_sprites.sprites.add s + line_sprites.add s + if in_link then link_sprites.add s - dx += advance + kerning + dx += (advance + kerning) * scale prev_char = c + + text_width = text_width.max(dx) + + if not line_height_counted then + # Increase `height` only once per line iff there's a caracter + line_height_counted = true + height += line_height + end + end + + justify(line_sprites, text_sprites.align, dx) + + # valign + if text_sprites.valign != 0.0 then + var d = (-dy+line_height) * text_sprites.valign + for s in text_sprites.sprites do s.center.y += d end + + text_sprites.width = text_width.max(dx) + text_sprites.height = height end - # Character replacing other charactesr missing from the font + # Character replacing other characters missing from the font private var replacement_char: nullable Char is lazy do for c in "�?".chars do if desc.chars.keys.has(c) then return c end return null end + + private fun advance(prev_char: nullable Char, char: Char): Float + do + var char_info = desc.chars[char] + var kerning = desc.kerning(prev_char, char) + return char_info.xadvance + kerning + end + + private fun justify(line_sprites: Array[Sprite], align: Float, line_width: Float) + do + var dx = -line_width*align + for s in line_sprites do s.center.x += dx + line_sprites.clear + end end