Yes, I’m afraid you’ll have to replace each text in your code. But the replacement consists of:
Never create a text including the ‘parent’ parameter in the options of display.newText. Instead, do something like this:
if platform == "html5" then
myGroup:insert(myText.raw)
else
myGroup:insert(myText)
end
Other considerations:
To create your bitmap font, first review your entire code and identify the largest font size you use. Then, in the settings of the bitmap font generator software, set the font size to double the maximum size used in your app.
The configuration I use in bitmapfont64.exe (the version I downloaded from https://www.angelcode.com/products/bmfont/) to export the bitmap font is as follows:
In Font Settings:
- Charset: Unicode
- Match char height: Checked
- Render from TrueType outline: Do NOT check.
- ClearType: Checked (improves rendering quality)
- Font Smoothing: Checked (enhances text appearance)
- Super Sampling: Checked - choose level: 2 (use this when text quality is a priority, such as in HTML5 where texts can appear blurry)
In Export Options:
- Paddings: 2 on each side
- Spacing: 0 for all
- Equalize the cell height: Checked (this ensures the text is properly centered along the ‘y’ axis).
- Width and Height: Width and height of the bitmap font’s image sheet
- Bit Depth: Select 32 bits
-
Pack chars in multiple channels: Checked (enabled)
…
Last but not least, I’ve attached the code for ponyfont.lua below with some changes I implemented to fix several bugs:
-- ponyfont 0.3
-- A bitmap font loader/render for CoronaSDK
local M = {}
local cache = {} -- cache for loaded fonts
-- If you use UTF-8 characters, you will need to add this plugin to your
-- build settings
-- https://docs.coronalabs.com/plugin/utf8/index.html
local utf8 -- or require("lua-utf8") via luarocks
-- Solar2D's utf8 plugin does not have html5 support
if platform == "html5" then
local floor = math.floor
utf8 = {}
function utf8.codes(str)
local pos = 1 -- starting position in the string
str = tostring(str) -- convert to a string (can be a number)
local _len = #str -- string length
return function()
if pos <= _len then
local code
local byte = string.byte(str, pos)
if byte < 0x80 then
code = byte
pos = pos + 1
elseif byte < 0xE0 then
code = (byte % 0x40) * 0x40 + string.byte(str, pos + 1) % 0x80
pos = pos + 2
elseif byte < 0xF0 then
code = (byte % 0x20) * 0x1000 + (string.byte(str, pos + 1) % 0x80) * 0x40 + string.byte(str, pos + 2) % 0x80
pos = pos + 3
else
code = (byte % 0x10) * 0x40000 + (string.byte(str, pos + 1) % 0x80) * 0x1000 + (string.byte(str, pos + 2) % 0x80) * 0x40 + string.byte(str, pos + 3) % 0x80
pos = pos + 4
end
return pos - 1, code
end
end
end -- end of 'utf8.codes()'
utf8.char = function(...)
local function encode(code)
if type(code) ~= "number" then
code = tonumber(code)
if not code then
--error("Expected a number, got " .. type(code) .. " (" .. tostring(code) .. ")")
end
end
if code <= 0x7F then
return string.char(code)
elseif code <= 0x7FF then
return string.char(
0xC0 + floor(code / 0x40),
0x80 + (code % 0x40)
)
elseif code <= 0xFFFF then
return string.char(
0xE0 + floor(code / 0x1000),
0x80 + (floor(code / 0x40) % 0x40),
0x80 + (code % 0x40)
)
elseif code <= 0x10FFFF then
return string.char(
0xF0 + floor(code / 0x40000),
0x80 + (floor(code / 0x1000) % 0x40),
0x80 + (floor(code / 0x40) % 0x40),
0x80 + (code % 0x40)
)
else
--error("Unicode code point out of range: " .. tostring(code))
end
end
local chars = {}
for _, code in ipairs({...}) do
if type(code) ~= "number" then
code = tonumber(code)
if not code then
--error("Expected a number, got " .. type(code) .. " (" .. tostring(code) .. ")")
end
end
table.insert(chars, encode(code))
end
return table.concat(chars)
end
else
utf8 = require("plugin.utf8") -- or require("lua-utf8") via luarocks
end
-- property update events by Jonathan Beebe
-- https://coronalabs.com/blog/2012/05/01/tutorial-property-callbacks/
local function addPropertyUpdate(obj)
local t = {}
t.raw = obj
local mt = {
__index = function(_,k)
if k == "raw" then
return rawget(t, "raw")
end
-- pass method and property requests to the display object
if type(obj[k]) == 'function' then
return function(...) arg[1] = obj; obj[k](unpack(arg)) end
else
return obj[k]
end
end,
__newindex = function(tb,k,v)
-- dispatch event before property update
local event = {
name = "propertyUpdate",
target=tb,
key=k,
value=v
}
obj:dispatchEvent(event)
-- update the property on the display object
obj[k] = v
end
}
setmetatable(t, mt)
return t
end
local function parseFilename(filename)
return string.match(filename,"(.-)([^\\/]-%.?([^%.\\/]*))$")
end
local function extract(s, p)
return string.match(s, p), string.gsub(s, p, '', 1)
end
function M.newText(options)
options = options or {}
-- Modified .fnt loading code from bmf.lua
-- Contacted author and he released under CC0
local function loadFont(name)
-- load the fnt
local path, filename, ext = parseFilename(name)
local font = { info = {}, spritesheets = {}, sprites = {}, chars = {}, kernings = {} }
local contents = io.lines(system.pathForFile(path .. filename, system.ResourceDirectory))
for line in contents do
local t = {}
local tag
tag, line = extract(line, '^%s*([%a_]+)%s*')
while string.len(line) > 0 do
local k, v
k, line = extract(line, '^([%a_]+)=')
if not k then break end
v, line = extract(line, '^"([^"]*)"%s*')
if not v then
v, line = extract(line, '^([^%s]*)%s*')
end
if not v then break end
t[k] = v
end
if tag == 'info' or tag == 'common' then
for k, v in pairs(t) do font.info[k] = v end
elseif tag == 'page' then
font.spritesheets[1 + t.id] = { file = t.file, frames = {} }
elseif tag == 'char' then
t.letter = utf8.char(t.id)
font.chars[t.letter] = {}
for k, v in pairs(t) do
font.chars[t.letter][k] = v
end
if 0 + font.chars[t.letter].width > 0 and 0 + font.chars[t.letter].height > 0 then
font.spritesheets[1 + t.page].frames[#font.spritesheets[1 + t.page].frames + 1] = {
x = 0 + t.x,
y = 0 + t.y,
width = 0 + t.width,
height = 0 + t.height,
}
font.sprites[t.letter] = {
spritesheet = 1 + t.page,
frame = #font.spritesheets[1 + t.page].frames
}
end
elseif tag == 'kerning' then
font.kernings[utf8.char(t.first) .. utf8.char(t.second)] = 0 + t.amount -- old code
end
end
for k, v in pairs(font.spritesheets) do
font.spritesheets[k].sheet = graphics.newImageSheet(path .. v.file, v)
end
for k, v in pairs(font.sprites) do
font.sprites[k].frame = v.frame
end
return font
end -- end of 'loadFont'
-- create displayGroup instance
local instance = display.newGroup()
if options.parent then
options.parent:insert(instance)
end
-- load font if not in cache
local fontFile = options.font or "default"
if not cache[fontFile] then
cache[fontFile] = loadFont(fontFile)
end
instance.bitmapFont = cache[fontFile]
-- Brand-spanking new render code with scaling to fontSize,
-- word wrapping to width and general performance and
-- readability updates
function instance:anchor()
local w,h = self._width or self.width, self.height
local x,y = self.anchorX or 0.5, self.anchorY or 0.5
for i = self.numChildren, 1, -1 do
if self[i]._x and self[i]._y then
self[i].x = self[i]._x
self[i].y = self[i]._y
self[i]:translate(-w * x, -h * y)
end
end
end
function instance:justify(align)
if not self._width then return false end
-- grab first letter
local letter = self[1]
if not letter then return false end
-- default
align = self.align or "left"
local x, last, lastX, w = 0, 1, letter.x, self._width or self.width
local currY
-- push stuff around
local iFinal = self.numChildren
for i = 1, iFinal do -- iterates over all characters in the text
if self[i]._x and self[i]._y then
x = self[i].x + self[i].width
local newLine = false
if i > 1 and ( self[i].y - currY ) > 3 then -- to detect line jump
newLine = true
end
currY = self[i].y
--if ( x < lastX ) or ( i == iFinal ) then -- original code
if newLine or ( i == iFinal ) then -- wrapped
print ("new line")
-- diff is based on assigned width
local diff = w - lastX
if align == "right" then
diff = diff - w*0.5
elseif align == "center" then
diff = diff * 0.5 - w*0.25
else
diff = 0
end
local jFinal = i - ( (i == self.numChildren) and 0 or 1 )
-- positions characters for the same line
for j = last, jFinal do
self[j]:translate(diff,0)
self[i].xScale = self[i]._xScale
self[i].yScale = self[i]._yScale
end
last = i
end
lastX = x - self[i].width*0.5
end
end
end -- end of justify
function instance:render( size )
local text = self.text
local font = self.bitmapFont
local info = font.info
local scale = self.fontSize / info.size
if size then
scale = self.size / info.size
end
if self.size ~= self.fontSize then
scale = self.size / info.size
end
local fill = self.fill or {1}
-- clear previous text
for i = self.numChildren, 1, -1 do
display.remove(self[i])
end
-- locals
local x, y = 0, 0
local last = ''
local lastWord = 0
local function kern(x, pair)
if font.kernings[pair] then
x = x + font.kernings[pair]
end
return x
end
if text then
local prevLetter = nil
for _, codepoint in utf8.codes(text) do
local letter = utf8.char(codepoint)
if letter == '\n' then -- newline
x, y = 0, y + info.lineHeight
elseif font.chars[letter] then
if letter ~= " " and tonumber(font.chars[letter].width) > 0 and tonumber(font.chars[letter].height) > 0 then
local glyph = display.newImage(font.spritesheets[font.sprites[letter].spritesheet].sheet, font.sprites[letter].frame)
x = kern(x, last .. letter)
glyph.anchorX, glyph.anchorY = 0, 0
glyph.xScale = scale
glyph.yScale = scale
glyph.x = scale * (font.chars[letter].xoffset + x)
glyph.y = scale * (font.chars[letter].yoffset + y)
glyph._x = glyph.x -- original offset from self's x
glyph._y = glyph.y -- original offset from self's y
glyph._xScale = glyph.xScale
glyph._yScale = glyph.yScale
glyph.chr = letter
last = letter
lastWord = lastWord + 1
self:insert(glyph)
if fill[2] then
glyph:setFillColor(fill[1], fill[2], fill[3])
else
glyph:setFillColor(fill[1])
end
elseif letter == " " then
lastWord = 0 -- save x of last word
end
x = x + font.chars[letter].xadvance
if self._width and (x * scale) > self._width then
-- jump to a new line
--print ("NEW LINE")
x = 0
y = y + info.lineHeight
local init = self.numChildren - lastWord + 1
for i = init, self.numChildren do
local glyph = self[i]
local wrapped = glyph.chr -- current char
x = kern(x, last .. wrapped)
glyph.x = scale * (font.chars[wrapped].xoffset + x)
glyph.y = scale * (font.chars[wrapped].yoffset + y)
glyph.xScale = scale
glyph.yScale = scale
glyph._x = glyph.x -- orginal offset from self's x
glyph._y = glyph.y -- orginal offset from self's y
x = x + font.chars[wrapped].xadvance
last = wrapped
end
end
end
prevLetter = letter
end
end
self:anchor()
self:justify()
end -- end of render
instance = addPropertyUpdate(instance)
function instance:propertyUpdate(event)
if event.key == "text" then
self.text = event.value
self:render()
elseif event.key == "anchorX" then
self.anchorX = event.value
self:anchor()
elseif event.key == "anchorY" then
self.anchorY = event.value
self:anchor()
elseif event.key == "align" then
self.align = event.value
self:justify()
elseif event.key == "fontSize" then
self.fontSize = event.value
self:render()
elseif event.key == "width" then
self._width = event.value
self:render()
elseif event.key == "x" then
self._x = event.value
self.x = self._x
elseif event.key == "y" then
self._y = event.value
self.y = self._y
elseif event.key == "size" then
self.size = event.value
self:render(true)
elseif event.key == "fill" then
self.fill = event.value
self:render()
end
end
function instance:finalize(event)
self:removeEventListener("propertyUpdate")
end
-- set options
instance.align = options.align or "left"
instance.fontSize = options.fontSize or 24
instance.size = options.size or instance.fontSize
instance.x = options.x or 0
instance.y = options.y or 0
instance._x, instance._y = instance.x, instance.y
instance._width = options.width
instance.text = options.text
instance.fill = {1}
instance:render()
-- add listeners
instance:addEventListener("propertyUpdate")
instance:addEventListener("finalize")
function instance:updateSize(newSize)
if newSize and type(newSize) == "number" then
local scaleFactor = newSize / self.fontSize
for i = 1, self.numChildren do
self[i].xScale = scaleFactor
self[i].yScale = scaleFactor
end
self.fontSize = newSize
end
end
return instance
end -- end of 'M.newText()' function
return M