1-------------------------------------------------------------------------------- 2-------------------------------------------------------------------------------- 3-- 4-- file: fonts.lua 5-- brief: font handler, with automatic texture atlas generation 6-- author: Dave Rodgers 7-- 8-- Copyright (C) 2007. 9-- Licensed under the terms of the GNU GPL, v2 or later. 10-- 11-------------------------------------------------------------------------------- 12-------------------------------------------------------------------------------- 13 14if (fontHandler ~= nil) then 15 return fontHandler 16end 17 18-- ":n:" sets it to nearest texture filtering 19local DefaultFontName = ":n:" .. LUAUI_DIRNAME .. "Fonts/FreeMonoBold_12" 20 21Spring.CreateDir(LUAUI_DIRNAME .. 'Fonts') 22 23 24-------------------------------------------------------------------------------- 25-------------------------------------------------------------------------------- 26 27-- Automatically generated local definitions 28 29local glCallList = gl.CallList 30local glColor = gl.Color 31local glCreateList = gl.CreateList 32local glDeleteList = gl.DeleteList 33local glDeleteTexture = gl.DeleteTexture 34local glPopMatrix = gl.PopMatrix 35local glPushMatrix = gl.PushMatrix 36local glTexRect = gl.TexRect 37local glTexture = gl.Texture 38local glTranslate = gl.Translate 39local spGetLastUpdateSeconds = Spring.GetLastUpdateSeconds 40 41 42-------------------------------------------------------------------------------- 43-------------------------------------------------------------------------------- 44-- 45-- Local speedups 46-- 47 48local floor = math.floor 49local strlen = string.len 50local strsub = string.sub 51local strbyte = string.byte 52local strchar = string.char 53local strfind = string.find 54local strgmatch = string.gmatch 55 56 57-------------------------------------------------------------------------------- 58-------------------------------------------------------------------------------- 59 60local fonts = {} 61local activeFont = nil 62local defaultFont = nil 63 64local caching = true 65local useFloor = true 66 67local timeStamp = 0 68local lastUpdate = 0 69 70local debug = false 71local origPrint = print 72local print = function(...) 73 if (debug) then 74 origPrint(...) 75 end 76end 77 78 79-------------------------------------------------------------------------------- 80-------------------------------------------------------------------------------- 81 82local function HaveFontFiles(fontName) 83 if (VFS.FileExists(fontName .. '.lua') and 84 VFS.FileExists(fontName .. '.png')) then 85 return true 86 end 87 return false 88end 89 90 91local function CreateFontFiles(fontName) 92 local _, _, name, size = string.find(fontName, '^(.*)_(%d*)$') 93 if ((not name) or (not size)) then 94 return false 95 end 96 97 local fullName = name .. '.ttf' 98 local inData = VFS.LoadFile(fullName) 99 if (not inData) then 100 fullName = name .. '.otf' 101 inData = VFS.LoadFile(fullName) 102 end 103 if (not inData) then 104 return false 105 end 106 107 print('CreateFontFiles = ' .. fullName .. ', ' .. size) 108 109 return 110 Spring.MakeFont(fullName, { 111 inData = inData, 112 height = tonumber(size), 113 minChar = 0, 114 maxChar = 255, 115 --[[ 116 texWidth = 117 outlineMode = 118 outlineRadius = 119 outlineWeight = 120 padding = 121 spacing = 122 debug = 123 --]] 124 }) 125end 126 127 128local function LoadFontSpecs(fontName) 129 local specFile = fontName .. ".lua" 130 local text = VFS.LoadFile(specFile) 131 if (text == nil) then 132 return nil 133 end 134 local chunk, err = loadstring(text, specFile) 135 if (not chunk) then 136 return nil 137 end 138 local fontSpecs = chunk() 139 140 print('fontSpecs.srcFile = ' .. fontSpecs.srcFile) 141 print('fontSpecs.family = ' .. fontSpecs.family) 142 print('fontSpecs.style = ' .. fontSpecs.style) 143 print('fontSpecs.size = ' .. fontSpecs.height) 144 print('fontSpecs.yStep = ' .. fontSpecs.yStep) 145 print('fontSpecs.xTexSize = ' .. fontSpecs.xTexSize) 146 print('fontSpecs.yTexSize = ' .. fontSpecs.yTexSize) 147 148 return fontSpecs 149end 150 151 152-------------------------------------------------------------------------------- 153-------------------------------------------------------------------------------- 154 155local function MakeDisplayLists(fontSpecs) 156 local lists = {} 157 local xs = fontSpecs.xTexSize 158 local ys = fontSpecs.yTexSize 159 for _,gi in pairs(fontSpecs.glyphs) do 160 local list = glCreateList(function () 161 glTexRect(gi.oxn, gi.oyn, gi.oxp, gi.oyp, 162 gi.txn / xs, 1.0 - (gi.tyn / ys), 163 gi.txp / xs, 1.0 - (gi.typ / ys)) 164 glTranslate(gi.adv, 0, 0) 165 end) 166 lists[gi.num] = list 167 end 168 return lists 169end 170 171 172local function MakeOutlineDisplayLists(fontSpecs) 173 local lists = {} 174 local tw = fontSpecs.xTexSize 175 local th = fontSpecs.yTexSize 176 177 for _,gi in pairs(fontSpecs.glyphs) do 178 local w = gi.xmax - gi.xmin 179 local h = gi.ymax - gi.ymin 180 local txn = gi.xmin / tw 181 local tyn = gi.ymax / th 182 local txp = gi.xmax / tw 183 local typ = gi.ymin / th 184 185 local list = glCreateList(function () 186 glTranslate(gi.initDist, 0, 0) 187 188 glColor(0, 0, 0, 0.75) 189 local o = 2 190 glTexRect( o, o, w, h, txn, tyn, txp, typ) 191 glTexRect(-o, o, w, h, txn, tyn, txp, typ) 192 glTexRect( o, 0, w, h, txn, tyn, txp, typ) 193 glTexRect(-o, 0, w, h, txn, tyn, txp, typ) 194 glTexRect( o, -o, w, h, txn, tyn, txp, typ) 195 glTexRect(-o, -o, w, h, txn, tyn, txp, typ) 196 glTexRect( 0, o, w, h, txn, tyn, txp, typ) 197 glTexRect( 0, -o, w, h, txn, tyn, txp, typ) 198 199 glColor(1, 1, 1, 1) 200 glTexRect( 0, 0, w, h, txn, tyn, txp, typ) 201 202 glTranslate(gi.width + gi.whitespace, 0, 0) 203 end) 204 205 lists[gi.num] = list 206 end 207 return lists 208end 209 210 211-------------------------------------------------------------------------------- 212 213local function StripColorCodes(text) 214 local stripped = "" 215 for txt, color in strgmatch(text, "([^\255]*)(\255?.?.?.?)") do 216 if (strlen(txt) > 0) then 217 stripped = stripped .. txt 218 end 219 end 220 return stripped 221end 222 223 224-------------------------------------------------------------------------------- 225 226local function RawGetTextWidth(text) 227 local specs = activeFont.specs 228 local w = 0 229 for i = 1, strlen(text) do 230 local c = strbyte(text, i) 231 local glyphInfo = specs.glyphs[c] 232 if (not glyphInfo) then 233 glyphInfo = specs.glyphs[32] 234 end 235 if (glyphInfo) then 236 w = w + glyphInfo.adv 237 end 238 end 239 return w 240end 241 242 243local function GetTextWidth(text) 244 -- return the cached value if available 245 local cacheTextData = activeFont.cache[text] 246 if (cacheTextData) then 247 local width = cacheTextData[3] 248 if (width) then 249 return width 250 end 251 end 252 local stripped = StripColorCodes(text) 253 return RawGetTextWidth(stripped) 254end 255 256 257local function CalcTextHeight(text) 258 return activeFont.specs.height 259end 260 261 262-------------------------------------------------------------------------------- 263 264local function RawDraw(text) 265 local lists = activeFont.lists 266 for i = 1, strlen(text) do 267 local c = strbyte(text, i) 268 local list = lists[c] 269 if (list) then 270 glCallList(list) 271 else 272 glCallList(lists[strbyte(" ", 1)]) 273 end 274 end 275end 276 277 278local function RawColorDraw(text) 279 for txt, color in strgmatch(text, "([^\255]*)(\255?.?.?.?)") do 280 if (strlen(txt) > 0) then 281 RawDraw(txt) 282 end 283 if (strlen(color) == 4) then 284 glColor(strbyte(color, 2) / 255, 285 strbyte(color, 3) / 255, 286 strbyte(color, 4) / 255) 287 end 288 end 289end 290 291 292local function DrawNoCache(text, x, y) 293 if (not x) then 294 RawDraw(text) 295 else 296 glPushMatrix() 297 glTranslate(x, y, 0) 298 glTexture(activeFont.image) 299 RawColorDraw(text) 300 glTexture(false) 301 glPopMatrix() 302 end 303end 304 305 306local function Draw(text, x, y) 307 if (not caching) then 308 DrawNoCache(text, x, y) 309 return 310 end 311 312 local cacheTextData = activeFont.cache[text] 313 if (not cacheTextData) then 314 glTexture(activeFont.image) -- else we would _recreate_ the texture each call to the displaylist! 315 local textList = glCreateList(function() 316 glTexture(activeFont.image) 317 RawColorDraw(text) 318 glTexture(false) 319 end) 320 cacheTextData = { textList, timeStamp } -- param [3] is the width 321 activeFont.cache[text] = cacheTextData 322 else 323 cacheTextData[2] = timeStamp -- refresh the timeStamp 324 end 325 326 if (not x) then 327 glCallList(cacheTextData[1]) 328 else 329 glPushMatrix() 330 if (useFloor) then 331 glTranslate(floor(x), floor(y), 0) 332 else 333 glTranslate(x, y, 0) 334 end 335 glCallList(cacheTextData[1]) 336 glPopMatrix() 337 end 338end 339 340 341-------------------------------------------------------------------------------- 342-------------------------------------------------------------------------------- 343 344local function DrawRight(text, x, y) 345 local width = GetTextWidth(text) 346 glPushMatrix() 347 glTranslate(-width, 0, 0) 348 Draw(text, x, y) 349 glPopMatrix() 350end 351 352 353local function DrawCentered(text, x, y) 354 local width = GetTextWidth(text) 355 local halfWidth 356 if (useFloor) then 357 halfWidth = floor(width * 0.5) 358 else 359 halfWidth = width * 0.5 360 end 361 glPushMatrix() 362 glTranslate(-halfWidth, 0, 0) 363 Draw(text, x, y) 364 glPopMatrix() 365end 366 367 368-------------------------------------------------------------------------------- 369-------------------------------------------------------------------------------- 370 371local function LoadFont(fontName) 372 print('LoadFont: ' .. fontName) 373 374 if (fonts[fontName]) then 375 return nil -- already loaded 376 end 377 378 local baseName = fontName 379 local _,_,options,bn = strfind(fontName, "(:.-:)(.*)") 380 if (options) then 381 baseName = bn 382 else 383 options = '' 384 end 385 386 if (not HaveFontFiles(baseName)) then 387 CreateFontFiles(baseName) 388 end 389 390 local fontSpecs = LoadFontSpecs(baseName) 391 if (not fontSpecs) then 392 return nil -- bad specs 393 end 394 395 if (not VFS.FileExists(baseName .. ".png")) then 396 return nil -- missing texture 397 end 398 399 local fontLists 400 if (strfind(options, "o")) then 401 fontLists = MakeOutlineDisplayLists(fontSpecs) 402 else 403 fontLists = MakeDisplayLists(fontSpecs) 404 end 405 if (not fontLists) then 406 return nil -- bad display lists 407 end 408 409 local font = {} 410 font.name = fontName 411 font.base = baseName 412 font.opts = options 413 font.specs = fontSpecs 414 font.lists = fontLists 415 font.cache = {} 416 font.image = options .. baseName .. ".png" 417 418 fonts[fontName] = font 419 420 return font 421end 422 423 424local function UseFont(fontName) 425 local font = fonts[fontName] 426 if (font) then 427 activeFont = font 428 return true 429 end 430 431 font = LoadFont(fontName) 432 if (font) then 433 activeFont = font 434 print("Loaded font: " .. fontName) 435 return true 436 end 437 438 return false 439end 440 441 442local function UseDefaultFont() 443 activeFont = defaultFont 444end 445 446 447local function SetDefaultFont(name) 448 local tmpFont = activeFont 449 if (UseFont(name)) then 450 DefaultFontName = name 451 defaultFont = activeFont 452 end 453 activeFont = tmpFont 454end 455 456 457-------------------------------------------------------------------------------- 458-------------------------------------------------------------------------------- 459 460local function FreeCache(fontName) 461 local font = (fontName == nil) and activeFont or fonts[fontName] 462 if (not font) then 463 return 464 end 465 for text,data in pairs(font.cache) do 466 glDeleteList(data[1]) 467 end 468end 469 470 471local function FreeFont(fontName) 472 local font = (fontName == nil) and activeFont or fonts[fontName] 473 if (not font) then 474 return 475 end 476 477 for _,list in pairs(font.lists) do 478 glDeleteList(list) 479 end 480 for text,data in pairs(font.cache) do 481 glDeleteList(data[1]) 482 end 483 glDeleteTexture(font.image) 484 485 fonts[font.name] = nil 486end 487 488 489local function FreeFonts() 490 for fontName in pairs(fonts) do 491 FreeFont(fontName) 492 end 493end 494 495 496local function Update() 497 timeStamp = timeStamp + spGetLastUpdateSeconds() 498 if (timeStamp < (lastUpdate + 1.0)) then 499 return -- only update every 1.0 seconds 500 end 501 502 local killTime = (timeStamp - 3.0) 503 for fontName, font in pairs(fonts) do 504 local killList = {} 505 for text,data in pairs(font.cache) do 506 if (data[2] < killTime) then 507 glDeleteList(data[1]) 508 table.insert(killList, text) 509 print(fontName .. " removed string list(" .. data[1] .. ") " .. text) 510 end 511 end 512 for _,text in ipairs(killList) do 513 font.cache[text] = nil 514 end 515 end 516 517 lastUpdate = timeStamp 518end 519 520 521-------------------------------------------------------------------------------- 522-------------------------------------------------------------------------------- 523 524UseFont(DefaultFontName) 525defaultFont = activeFont 526 527 528local FH = {} 529 530FH.Update = Update 531 532FH.UseFont = UseFont 533FH.UseDefaultFont = UseDefaultFont 534FH.SetDefaultFont = SetDefaultFont 535 536FH.GetFontName = function() return activeFont.name end 537FH.GetFontSize = function() return activeFont.specs.height end 538FH.GetFontYStep = function() return activeFont.specs.yStep end 539FH.GetTextWidth = GetTextWidth 540 541FH.Draw = Draw 542FH.DrawRight = DrawRight 543FH.DrawCentered = DrawCentered 544 545FH.StripColors = StripColorCodes 546 547FH.FreeFont = FreeFont 548FH.FreeFonts = FreeFonts 549FH.FreeCache = FreeCache 550 551FH.CacheState = function() return caching end 552FH.EnableCache = function() caching = true end 553FH.DisableCache = function() caching = false end 554 555fontHandler = FH -- make it global 556 557return FH 558 559-------------------------------------------------------------------------------- 560-------------------------------------------------------------------------------- 561