Last active
January 1, 2016 15:38
-
-
Save tomekc/8165184 to your computer and use it in GitHub Desktop.
Bitmap font support in Corona SDK, compatible with Graphics 2.0
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module( ..., package.seeall ) | |
-- AngelCode bitmap font support | |
-- Updated for Graphics 2.0 | |
-- Download sprite module from https://github.com/coronalabs/framework-sprite-legacy/raw/master/sprite.lua | |
local sprite = require( "sprite" ) | |
-- Specify an Angelcode format bitmap font definition file (".FNT") | |
-- The spritesheet(s) that this file references need to be located in the resource directory. | |
-- Return value is a font object that can be used when calling newString | |
function loadFont( fntFile, path ) | |
local function extract( s, p ) | |
return string.match( s, p ), string.gsub( s, p, '', 1 ) | |
end | |
local path = path or "" | |
local font = { info = {}, spritesheets = {}, sprites = {}, chars = {}, kernings = {} } | |
local readline = io.lines( system.pathForFile( path .. fntFile, system.ResourceDirectory ) ) | |
for line in readline 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 = string.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 ] = { | |
--textureRect = { x = 0 + t.x, y = 0 + t.y, width = -1 + t.width, height = -1 + t.height }, --CLF removed the -1, it was causing issues with fonts with borders | |
textureRect = { x = 0 + t.x, y = 0 + t.y, width = t.width, height = t.height }, | |
spriteSourceSize = { width = 0 + t.width, height = 0 + t.height }, | |
spriteColorRect = { x = 0, y = 0, width = -1 + t.width, height = -1 + t.height }, | |
spriteTrimmed = true | |
} | |
font.sprites[ t.letter ] = { | |
spritesheet = 1 + t.page, | |
frame = #font.spritesheets[ 1 + t.page ].frames | |
} | |
end | |
elseif( tag == 'kerning' ) then | |
font.kernings[ string.char( t.first ) .. string.char( t.second ) ] = 0 + t.amount | |
end | |
end | |
for k, v in pairs( font.spritesheets ) do | |
font.spritesheets[ k ].sheet = sprite.newSpriteSheetFromData( path..v.file, v ) | |
end | |
for k, v in pairs( font.sprites ) do | |
font.sprites[ k ] = sprite.newSpriteSet( font.spritesheets[ v.spritesheet ].sheet, v.frame, 1 ) | |
end | |
return font | |
end | |
-- extend an object with accessor behaviors | |
local function accessorize( t ) | |
local mt = getmetatable( t ) | |
setmetatable( t, { | |
__index = function( t, k ) | |
if rawget( t, 'get_'..k ) then | |
return rawget(t, 'get_'..k )( t, k ) | |
elseif rawget( t, 'raw_'..k ) then | |
return rawget( t, 'raw_'..k ) | |
elseif mt.__index then | |
return mt.__index( t, k ) | |
else | |
return nil | |
end | |
end, | |
__newindex = function( t, k, v ) | |
if rawget( t, 'set_'..k ) then | |
rawget( t, 'set_'..k )( t, k, v ) | |
elseif rawget( t, 'raw_'..k ) then | |
rawset( t, 'raw_'..k, v ) | |
elseif mt.__newindex then | |
mt.__newindex( t, k, v ) | |
else | |
rawset( t, 'raw_'..k, v ) | |
end | |
end, | |
} ) | |
end | |
-- extend an object with cascading removeSelf | |
local function removerize( t ) | |
local old = t.removeSelf | |
t.removeSelf = function( o ) | |
for i = o.numChildren, 1, -1 do o[ i ]:removeSelf() end | |
old( o ) | |
end | |
end | |
-- Pass a font object (obtained from loadFont) and a string to render | |
-- Return value is a DisplayObject of the rendered string | |
-- object.font can be read/modifed | |
-- object.text can be read/modified | |
-- object.align can be read/modified - left/right/center (multiline not yet fully supported for non-left) | |
-- object.input( function(text), { filter = function(), max = 32 } ) | |
-- turns the object into a text input. | |
-- the callback is hit when the user presses "return" or the field losed focus. | |
-- this code is under development - more documentation will be added soon... | |
function newString( font, text ,origsize, newsize) | |
local finalScale = 100/origsize *newsize/100; | |
local obj = display.newGroup() | |
accessorize( obj ) | |
removerize( obj ) | |
obj.set_font = function( t, k, v ) | |
obj.raw_font = v | |
if t.text then | |
t.text = t.text | |
end | |
end | |
-- Set the alignment of the object | |
obj.set_align = function( t, k, v ) | |
local w = t.textWidth | |
if t.raw_align == 'right' then | |
for i = 1, t.numChildren do | |
t[ i ].x = t[ i ].x + w | |
end | |
elseif t.raw_align == 'center' then | |
for i = 1, t.numChildren do | |
t[ i ].x = t[ i ].x + math.floor( w * 0.5 ) | |
end | |
end | |
t.raw_align = v | |
if t.raw_align == 'right' then | |
for i = 1, t.numChildren do | |
t[ i ].x = t[ i ].x - w | |
end | |
elseif t.raw_align == 'center' then | |
for i = 1, t.numChildren do | |
t[ i ].x = t[ i ].x - math.floor( w * 0.5 ) | |
end | |
elseif t.raw_align ~= 'left' then | |
t.raw_align = 'left' | |
end | |
end | |
-- Set the text for the object | |
obj.set_text = function( t, k, v ) | |
if ( v == t.raw_text ) then | |
return | |
end | |
t.raw_text = v | |
for i = t.numChildren, 1, -1 do | |
t[i]:removeSelf() | |
end | |
local oldAlign = ( t.align or 'left' ) | |
t.align = 'left' | |
local x = 0; local y = 0 | |
local last = ''; local xMax = 0; local yMax = 0 | |
if t.raw_font then | |
for c in string.gmatch( t.raw_text..'\n', '(.)' ) do | |
if c == '\n' then | |
x = 0; y = y + t.raw_font.info.lineHeight | |
if y >= yMax then | |
yMax = y | |
end | |
elseif t.raw_font.chars[ c ] then | |
local rfc = t.raw_font.chars[ c ] | |
if 0 + t.raw_font.chars[ c ].width > 0 and 0 + t.raw_font.chars[ c ].height > 0 then | |
local letter = sprite.newSprite( t.raw_font.sprites[ c ] ) | |
if t.raw_font.kernings[ last .. c ] then | |
x = x + font.kernings[ last .. c ] | |
end | |
t:insert( letter ) | |
letter.anchorX = 0 | |
letter.anchorY = 0 | |
letter.x = t.raw_font.chars[ c ].xoffset + x | |
letter.y = t.raw_font.chars[ c ].yoffset - t.raw_font.info.base + y | |
last = c | |
end | |
--x = x + t.raw_font.chars[ c ].xadvance | |
x = x + t.raw_font.chars[ c ].xadvance + (t.raw_font.info.outline or 0) --CLF added support for outlines | |
if x >= xMax then | |
xMax = x | |
end | |
end | |
end | |
obj.textWidth = xMax | |
local background = display.newRect( 0, -t.raw_font.info.base, xMax, yMax ) | |
background.isBackground = true --CLF Added to support tinting of font. | |
obj:insert( background ) | |
background:setFillColor( 0, 0, 0, 0 ) | |
end | |
t.align = oldAlign | |
end | |
obj.input = function( f, args ) | |
-- spawn the text field invisibly | |
local field | |
-- Handle character insertion/deletion | |
local function char() | |
-- check if any character has been added or deleted | |
if field.text ~= '--' then | |
if string.len( field.text ) < 2 then | |
-- backspace was pressed | |
if string.len( obj.text ) > 0 then | |
obj.text = string.sub( obj.text, 1, -2 ) | |
end | |
else | |
-- some other key was pressed | |
obj.text = obj.text..string.sub( field.text, 3 ) | |
end | |
field.text = '--' | |
if args.filter then | |
obj.text = string.sub( args.filter( obj.text ), 1, (args.max or 32) ) | |
else | |
obj.text = string.sub( obj.text, 1, (args.max or 32) ) | |
end | |
end | |
end | |
Runtime:addEventListener( 'enterFrame', char ) | |
-- Handle the "done" phase | |
local function done( e ) | |
if e.phase == 'submitted' or e.phase == 'ended' then | |
native.setKeyboardFocus( nil ) | |
field:removeSelf() | |
Runtime:removeEventListener( 'enterFrame', char ) | |
f( text ) | |
end | |
end | |
field = native.newTextField( 0, 0, 240, 24, done ) | |
field.text = '--' | |
field.isVisible = false | |
native.setKeyboardFocus( field ) | |
end | |
obj.font = font | |
obj.align = 'left' | |
obj.text = (text or '') | |
obj.xScale = finalScale; | |
obj.yScale = finalScale; | |
return obj | |
end | |
function newParagraph(font, text, width) | |
local obj = display.newGroup() | |
accessorize( obj ) | |
removerize( obj ) | |
obj.set_font = function( t, k, v ) | |
obj.raw_font = v | |
--if t.text then t.text = t.text end | |
end | |
obj.set_paragraphWidth = function( t, k, v ) | |
obj.raw_paragraphWidth = v | |
--if t.text then t.text = t.text end | |
end | |
obj.set_tint = function( t, k, v ) | |
obj.raw_tint = v | |
for i = t.numChildren, 1, -1 do | |
for j = t[i].numChildren, 1, -1 do | |
if(not t[i][j].isBackground) then | |
if v[4] then | |
t[i][j]:setFillColor(v[1],v[2],v[3],v[4]) | |
else | |
t[i][j]:setFillColor(v[1],v[2],v[3]) | |
end | |
end | |
end | |
end | |
--if t.text then t.text = t.text end | |
end | |
obj.set_text = function(t, k, v) | |
--save the new raw text | |
t.raw_text = v | |
--remove all children | |
for i = t.numChildren, 1, -1 do | |
t[i]:removeSelf() | |
end | |
--determine the width of a space | |
local space = bmf.newString(font, " ") | |
local spaceWidth = space.raw_font.info.outline*-1 or 0 | |
space:removeSelf() | |
space = nil | |
--Create our word-wrapped paragraph. | |
local spaceLeft = t.raw_paragraphWidth | |
local x, y = obj.x, obj.y | |
for word, spacer in string.gmatch(v, "([^%s%-]+)([%s%-]*)") do | |
local w = bmf.newString(font, word..spacer) | |
if(w.width + spaceWidth) > spaceLeft then | |
spaceLeft = t.raw_paragraphWidth - w.width - spaceWidth | |
y = y + w.raw_font.info.lineHeight | |
x = obj.x | |
w.x = x | |
else | |
w.x = x + t.raw_paragraphWidth - spaceLeft | |
spaceLeft = spaceLeft - w.width - spaceWidth | |
end | |
w.y = y | |
obj:insert(w) | |
end | |
end | |
obj.paragraphWidth = width or display.viewableContentWidth | |
obj.text = text | |
obj.font = font | |
obj.tint = {255,255,255} | |
return obj | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment