From: Jean Privat Date: Sat, 17 Jun 2017 00:24:33 +0000 (-0400) Subject: Merge: gamnit: customize writing with BMFont X-Git-Url: http://nitlanguage.org?hp=-c Merge: gamnit: customize writing with BMFont Add format services to `TestSprites` implemented (only) by `BMFont`: `align, valign, wrap, max_width` and `max_height`. You can see the result in the screenshot of the new showcase/test program: ![screenshot from 2017-06-15 15 53 28](https://user-images.githubusercontent.com/208057/27199185-1b9a36dc-51e3-11e7-9700-8dde5724e98a.png) Pull-Request: #2499 Reviewed-by: Jean Privat --- 47a22e48af43fb1389f0f02da8f97e8a9365e574 diff --combined lib/gamnit/bmfont.nit index 2cd69d7,4d6520c..fa3a197 --- a/lib/gamnit/bmfont.nit +++ b/lib/gamnit/bmfont.nit @@@ -24,7 -24,7 +24,7 @@@ module bmfon private import dom - import font + intrude import font # BMFont description, parsed with `Text::parse_bmfont` or loaded as a `BMFontAsset` # @@@ -40,7 -40,7 +40,7 @@@ class BMFon 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 +67,16 @@@ # 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 +85,14 @@@ 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,25 -118,25 +118,25 @@@ en 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 @@@ -174,9 -181,9 +181,9 @@@ redef class Tex # """ # # 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.to_s == "" + # assert fnt.line_height == 80.0 + # assert fnt.kernings['A', 'C'] == -1.0 # assert fnt.chars['A'].page.path == "dir_in_assets/arial.png" # ~~~ fun parse_bmfont(dir: String): MaybeError[BMFont, Error] @@@ -208,16 -215,16 +215,16 @@@ 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 -245,10 +245,10 @@@ 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 -263,7 +263,7 @@@ 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,8 -277,7 +277,8 @@@ en # ~~~ # 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 # do @@@ -355,35 -361,85 +362,85 @@@ class BMFontAsse do var dx = 0.0 var dy = 0.0 + var text_width = 0.0 - var line_height = desc.line_height.to_f + var max_width = text_sprites.max_width + var max_height = text_sprites.max_height - var prev_char = null - for c in text do + var line_height = desc.line_height + var partial_line_skip = line_height * partial_line_mod.to_f + var line_sprites = new Array[Sprite] - var partial_line_mod = 0.4 + 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 prev_char = null continue else if c == pld then - dy -= line_height * partial_line_mod.to_f - prev_char = null + dy -= 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 + 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 + word_break = true + end + + # 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 then break + word_len += advance(prev_w, w) + prev_w = w + end + + # Would the line be too long? + if dx + word_len > max_width then + if text_sprites.wrap then + # Wrap + 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 + else + # Cut short + 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 + while c != '\n' and i < text.length - 1 do + i += 1 + c = text[i] + end + end + end + end + prev_char = null continue end @@@ -396,28 -452,53 +453,53 @@@ 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 + var y = dy - char_info.height/2.0 - char_info.yoffset 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) + text_sprites.sprites.add s + line_sprites.add s dx += advance + kerning prev_char = c + + text_width = text_width.max(dx) + 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 = dy + line_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