180 lines
5.3 KiB
Crystal
180 lines
5.3 KiB
Crystal
module Funfriend::FontMan
|
|
extend self
|
|
|
|
private def quoted_space_split(string : String)
|
|
accum = [] of String
|
|
chars = [] of Char
|
|
quoted = false
|
|
|
|
string.each_char do |char|
|
|
if !quoted && char == ' '
|
|
if (chars.count &.!= ' ') > 0
|
|
accum << chars.join
|
|
end
|
|
chars = [] of Char
|
|
else
|
|
if char == '"'
|
|
quoted = !quoted
|
|
end
|
|
chars << char
|
|
end
|
|
end
|
|
|
|
if (chars.count &.!= ' ') > 0
|
|
accum << chars.join
|
|
end
|
|
|
|
return accum
|
|
end
|
|
|
|
alias BMCommon = NamedTuple(line_height: Int32, base: Int32, scale_w: Int32, scale_h: Int32)
|
|
alias BMChar = NamedTuple(id: Int32, x: Int32, y: Int32, width: Int32, height: Int32, xoffset: Int32, yoffset: Int32, xadvance: Int32, letter: Char)
|
|
alias BMKerning = NamedTuple(first: Int32, second: Int32, amount: Int32)
|
|
alias BMSheet = NamedTuple(common: BMCommon, chars: Array(BMChar), kernings: Array(BMKerning))
|
|
|
|
def parse_bm(data : String) : BMSheet
|
|
common = nil
|
|
chars = [] of BMChar
|
|
kernings = [] of BMKerning
|
|
|
|
data.each_line do |line|
|
|
words = line.split
|
|
key = words[0]?
|
|
args_array = quoted_space_split(words[1..].join(" ")).map &.split('=', 2)
|
|
args = Hash.zip(args_array.map &.[0], args_array.map &.[1])
|
|
|
|
case key
|
|
when "common"
|
|
common = {
|
|
line_height: args["lineHeight"].to_i,
|
|
base: args["base"].to_i,
|
|
scale_w: args["scaleW"].to_i,
|
|
scale_h: args["scaleH"].to_i
|
|
}
|
|
when "char"
|
|
chars << {
|
|
id: args["id"].to_i,
|
|
x: args["x"].to_i,
|
|
y: args["y"].to_i,
|
|
width: args["width"].to_i,
|
|
height: args["height"].to_i,
|
|
xoffset: args["xoffset"].to_i,
|
|
yoffset: args["yoffset"].to_i,
|
|
xadvance: args["xadvance"].to_i,
|
|
letter: args["letter"].lchop('"').rchop('"')[0],
|
|
}
|
|
when "kerning"
|
|
kernings << {
|
|
first: args["first"].to_i,
|
|
second: args["second"].to_i,
|
|
amount: args["amount"].to_i,
|
|
}
|
|
end
|
|
end
|
|
|
|
return {
|
|
common: common.not_nil!,
|
|
chars: chars,
|
|
kernings: kernings,
|
|
}
|
|
end
|
|
|
|
# quick shorthand
|
|
def text_width(text : String, sheet : BMSheet)
|
|
text.chars.reduce 0 do |width, char|
|
|
bm_char = sheet[:chars].find! { |c| c[:letter] == char }
|
|
width + bm_char[:xadvance]
|
|
end
|
|
end
|
|
|
|
# TODO: kerning
|
|
def position_text(text : String, sheet : BMSheet)
|
|
positions = [] of NamedTuple(x: Int32, y: Int32, char: BMChar)
|
|
|
|
x = 0
|
|
text.each_char do |char|
|
|
bm_char = sheet[:chars].find! { |c| c[:letter] == char }
|
|
positions << {
|
|
x: x + bm_char[:xoffset],
|
|
y: sheet[:common][:base] - bm_char[:height] - bm_char[:yoffset] + (sheet[:common][:line_height] - sheet[:common][:base]),
|
|
char: bm_char,
|
|
}
|
|
x = x + bm_char[:xadvance]
|
|
end
|
|
|
|
return {
|
|
width: x,
|
|
height: sheet[:common][:line_height],
|
|
positions: positions,
|
|
}
|
|
end
|
|
|
|
def get_letter_crop(char : BMChar, sheet : BMSheet)
|
|
x = (char[:x] / sheet[:common][:scale_w]).to_f32
|
|
y = (char[:y] / sheet[:common][:scale_h]).to_f32
|
|
w = (char[:width] / sheet[:common][:scale_w]).to_f32
|
|
h = (char[:height] / sheet[:common][:scale_h]).to_f32
|
|
|
|
return {x, y, w, h}
|
|
end
|
|
|
|
def get_text_mesh(text : String, sheet : BMSheet, offset_x : Int32, offset_y : Int32, width : Int32, height : Int32)
|
|
vertices = [] of Float32
|
|
indices = [] of Int32
|
|
|
|
position_data = FontMan.position_text(text, sheet)
|
|
|
|
i = 0
|
|
position_data[:positions].each do |letter|
|
|
char = letter[:char]
|
|
x, y, w, h = get_letter_crop(char, sheet)
|
|
|
|
pos_x = ((letter[:x] + offset_x) / width ).to_f32 * 2 - 1
|
|
pos_w = (char[:width] / width ).to_f32 * 2
|
|
pos_y = ((letter[:y] + offset_y) / height).to_f32 * 2 - 1
|
|
pos_h = (char[:height] / height).to_f32 * 2
|
|
|
|
vertices += [
|
|
# ------ positions ------- texture coordinates
|
|
pos_x + pos_w, pos_y + pos_h, 0.0f32, x + w, y, # top right
|
|
pos_x + pos_w, pos_y, 0.0f32, x + w, y + h, # bottom right
|
|
pos_x, pos_y, 0.0f32, x, y + h, # bottom left
|
|
pos_x, pos_y + pos_h, 0.0f32, x, y, # top left
|
|
]
|
|
|
|
# Indices used for indexed draw calls. Each is used as an index into the four vertices.
|
|
indices += [
|
|
0, 1, 3, # first triangle
|
|
1, 2, 3 # second triangle
|
|
].map &.+ (i * 4)
|
|
|
|
i = i + 1
|
|
end
|
|
|
|
vertex_buffer = Buffer.new
|
|
element_buffer = Buffer.new
|
|
vertex_array = VertexArray.new
|
|
|
|
vertex_array.bind do
|
|
vertex_buffer.bind(Buffer::Target::Array) do |buffer, target|
|
|
GL.buffer_data_array(target, vertices, Buffer::UsageHint::StaticDraw)
|
|
vertex_array.define_attributes do |va|
|
|
va.attribute(3, DataType::Float, false)
|
|
va.attribute(2, DataType::Float, false)
|
|
end
|
|
end
|
|
|
|
# bind the element buffer to GL_ELEMENT_ARRAY_BUFFER
|
|
element_buffer.bind(Buffer::Target::ElementArray)
|
|
|
|
# Send index data to the GPU.
|
|
GL.buffer_data_array(Buffer::Target::ElementArray, indices, Buffer::UsageHint::StaticDraw)
|
|
end
|
|
|
|
# unbind the element buffer.
|
|
element_buffer.unbind
|
|
|
|
return {vertex_array, vertex_buffer}
|
|
end
|
|
end
|