Parse self as an XML BMFont description file

Reports only basic XML format errors, other errors may be ignored or cause a crash.

var desc = """
<font>
  <info face="arial" size="72" bold="0" italic="0" charset=""
        unicode="1" stretchH="100" smooth="1" aa="1" padding="2,2,2,2"
        spacing="0,0" outline="0"/>
  <common lineHeight="80" base="65" scaleW="4030" scaleH="231"
          pages="1" packed="0"/>
  <pages>
    <page id="0" file="arial.png"/>
  </pages>
  <chars count="3">
    <char id="65" x="2519" y="10" width="55" height="59" xoffset="0"
          yoffset="13" xadvance="48" page="0" chnl="15"/>
    <char id="66" x="2600" y="10" width="46" height="58" xoffset="5"
          yoffset="13" xadvance="48" page="0" chnl="15"/>
    <char id="67" x="2673" y="9" width="52" height="60" xoffset="4"
          yoffset="12" xadvance="52" page="0" chnl="15"/>
  </chars>
  <kernings count="1">
    <kerning first="65" second="67" amount="-1"/>
  </kernings>
</font>
"""

var fnt = desc.parse_bmfont("dir_in_assets").value
assert fnt.to_s == "<BMFont arial at 72.0 pt, 1 pages, 3 chars>"
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"

Property definitions

gamnit :: bmfont $ Text :: parse_bmfont
	# Parse `self` as an XML BMFont description file
	#
	# Reports only basic XML format errors, other errors may be ignored or
	# cause a crash.
	#
	# ~~~
	# var desc = """
	# <font>
	#   <info face="arial" size="72" bold="0" italic="0" charset=""
	#         unicode="1" stretchH="100" smooth="1" aa="1" padding="2,2,2,2"
	#         spacing="0,0" outline="0"/>
	#   <common lineHeight="80" base="65" scaleW="4030" scaleH="231"
	#           pages="1" packed="0"/>
	#   <pages>
	#     <page id="0" file="arial.png"/>
	#   </pages>
	#   <chars count="3">
	#     <char id="65" x="2519" y="10" width="55" height="59" xoffset="0"
	#           yoffset="13" xadvance="48" page="0" chnl="15"/>
	#     <char id="66" x="2600" y="10" width="46" height="58" xoffset="5"
	#           yoffset="13" xadvance="48" page="0" chnl="15"/>
	#     <char id="67" x="2673" y="9" width="52" height="60" xoffset="4"
	#           yoffset="12" xadvance="52" page="0" chnl="15"/>
	#   </chars>
	#   <kernings count="1">
	#     <kerning first="65" second="67" amount="-1"/>
	#   </kernings>
	# </font>
	# """
	#
	# var fnt = desc.parse_bmfont("dir_in_assets").value
	# assert fnt.to_s == "<BMFont arial at 72.0 pt, 1 pages, 3 chars>"
	# 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
		# Parse XML
		var xml = to_xml
		if xml isa XMLError then
			var msg = "XML Parse Error: {xml.message}:{xml.location or else 0}"
			return new MaybeError[BMFont, Error](maybe_error=new Error(msg))
		end

		# Basic sanity check
		var roots = xml["font"]
		if roots.is_empty then
			var msg = "Error: the XML document doesn't declare the expected `font` root"
			return new MaybeError[BMFont, Error](maybe_error=new Error(msg))
		end

		# Expect the rest of the document to be well formatted
		var root = roots.first

		var info = root["info"].first
		assert info isa XMLAttrTag
		var info_map = info.attributes_to_map

		var common = root["common"].first
		assert common isa XMLAttrTag
		var common_map = common.attributes_to_map

		var fnt = new BMFont(
			info_map["face"],
			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_f,
			common_map["base"].to_f,
			common_map["scaleW"].to_f,
			common_map["scaleH"].to_f
		)

		# Pages / pixel data files
		var xml_pages = root["pages"].first
		for page in xml_pages["page"] do
			if not page isa XMLAttrTag then continue

			var attributes = page.attributes_to_map
			var file = dir / attributes["file"]
			fnt.pages[attributes["id"]] = new TextureAsset(file)
		end

		# Char description
		for item in root["chars"].first["char"] do
			if not item isa XMLAttrTag then continue

			var attributes = item.attributes_to_map
			var id = attributes["id"].to_i.code_point

			var c = new BMFontChar(
				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
		end

		# Kerning between two characters
		var kernings = root["kernings"]
		if kernings.not_empty then
			for item in kernings.first["kerning"] do
				if not item isa XMLAttrTag then continue

				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_f
				fnt.kernings[first, second] = amount
			end
		end

		return new MaybeError[BMFont, Error](fnt)
	end
lib/gamnit/bmfont.nit:156,2--275,4