mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-18 03:35:56 +02:00
big ass modding update
This commit is contained in:
+110
-34
@@ -1,41 +1,105 @@
|
||||
-- Text renderer using the real extracted font sheets and charmap.
|
||||
-- font.png holds glyph codes $80-$FF, font_extra.png $60-$7F (borders etc).
|
||||
-- Glyphs live on *pages*: font.png holds codes $80-$FF, font_extra.png
|
||||
-- $60-$7F (borders etc), and a mod registers more (a kana block at $100,
|
||||
-- a replacement sheet for an existing page) through the font registry,
|
||||
-- which merges into data.font.pages. A page may set its own `advance`
|
||||
-- for variable-width text; the default is the GB's flat 8px.
|
||||
-- The charmap is matched greedily (longest sequence first) so multi-byte
|
||||
-- UTF-8 chars and ligature glyphs like 'd 'l 's map to single glyphs.
|
||||
|
||||
local Assets = require("src.render.Assets")
|
||||
|
||||
local Font = {}
|
||||
|
||||
local GLYPH = 8
|
||||
|
||||
local state
|
||||
local loadedFrom
|
||||
|
||||
-- the two vanilla pages as the legacy def spells them, so a cache that
|
||||
-- predates the pages table still loads and a mod that registers only one
|
||||
-- page replaces just that one
|
||||
local function pagesOf(def)
|
||||
local pages = {}
|
||||
if def.image then
|
||||
pages.main = { image = def.image, base = def.mainBase or 0x80,
|
||||
glyphsPerRow = def.glyphsPerRow or 16 }
|
||||
end
|
||||
if def.imageExtra then
|
||||
pages.extra = { image = def.imageExtra, base = def.extraBase or 0x60,
|
||||
glyphsPerRow = def.glyphsPerRow or 16 }
|
||||
end
|
||||
for id, page in pairs(def.pages or {}) do
|
||||
if type(page) == "table" and page.image then pages[id] = page end
|
||||
end
|
||||
return pages
|
||||
end
|
||||
|
||||
function Font.load(data)
|
||||
loadedFrom = data
|
||||
local def = data.font
|
||||
local main = love.graphics.newImage(def.image)
|
||||
local extra = love.graphics.newImage(def.imageExtra)
|
||||
state = {
|
||||
def = def,
|
||||
main = main,
|
||||
extra = extra,
|
||||
mainQuads = {},
|
||||
extraQuads = {},
|
||||
byFirstByte = {},
|
||||
}
|
||||
local function buildQuads(img, quads)
|
||||
local iw, ih = img:getDimensions()
|
||||
local perRow = iw / 8
|
||||
for i = 0, perRow * (ih / 8) - 1 do
|
||||
quads[i] = love.graphics.newQuad((i % perRow) * 8,
|
||||
math.floor(i / perRow) * 8, 8, 8, iw, ih)
|
||||
state = { def = def, pages = {}, order = {}, byFirstByte = {} }
|
||||
for id, page in pairs(pagesOf(def)) do
|
||||
local ok, img = pcall(Assets.image, page.image)
|
||||
if ok then
|
||||
local iw, ih = img:getDimensions()
|
||||
local perRow = page.glyphsPerRow or math.floor(iw / GLYPH)
|
||||
local quads = {}
|
||||
for i = 0, perRow * math.floor(ih / GLYPH) - 1 do
|
||||
quads[i] = love.graphics.newQuad((i % perRow) * GLYPH,
|
||||
math.floor(i / perRow) * GLYPH, GLYPH, GLYPH, iw, ih)
|
||||
end
|
||||
local entry = { id = id, image = img, quads = quads,
|
||||
base = page.base, advance = page.advance or GLYPH }
|
||||
state.pages[id] = entry
|
||||
state.order[#state.order + 1] = entry
|
||||
end
|
||||
end
|
||||
buildQuads(main, state.mainQuads)
|
||||
buildQuads(extra, state.extraQuads)
|
||||
-- charmap comes sorted longest-first from the extractor; bucket by first
|
||||
-- byte for fast greedy matching
|
||||
for _, entry in ipairs(def.charmap) do
|
||||
-- highest base first: a code resolves against the last page that starts
|
||||
-- at or below it, which is exactly what the old main/extra chain did
|
||||
table.sort(state.order, function(a, b) return a.base > b.base end)
|
||||
|
||||
-- Bucket the charmap by first byte for fast greedy matching, longest
|
||||
-- sequence first *within* each bucket. The sort is ours rather than
|
||||
-- the extractor's: a mod's page ships its own entries and nothing has
|
||||
-- put them in length order.
|
||||
local function bucket(entry)
|
||||
if type(entry) ~= "table" or type(entry.seq) ~= "string"
|
||||
or entry.seq == "" then return end
|
||||
local b = entry.seq:byte(1)
|
||||
state.byFirstByte[b] = state.byFirstByte[b] or {}
|
||||
table.insert(state.byFirstByte[b], entry)
|
||||
end
|
||||
for _, entry in ipairs(def.charmap or {}) do bucket(entry) end
|
||||
for _, page in pairs(def.pages or {}) do
|
||||
for _, entry in ipairs(type(page) == "table" and page.charmap or {}) do
|
||||
bucket(entry)
|
||||
end
|
||||
end
|
||||
for _, entries in pairs(state.byFirstByte) do
|
||||
table.sort(entries, function(a, b) return #a.seq > #b.seq end)
|
||||
end
|
||||
|
||||
Font.BORDER = {}
|
||||
for key, code in pairs(Font.DEFAULT_BORDER) do Font.BORDER[key] = code end
|
||||
for key, code in pairs(def.border or {}) do Font.BORDER[key] = code end
|
||||
end
|
||||
|
||||
-- re-run load against the data it last saw, so hot reload picks up an
|
||||
-- edited sheet or a newly merged page
|
||||
function Font.invalidate()
|
||||
if loadedFrom then Font.load(loadedFrom) end
|
||||
end
|
||||
|
||||
Assets.register(Font.invalidate)
|
||||
|
||||
-- the page a glyph code draws from, or nil when nothing covers it
|
||||
local function pageFor(code)
|
||||
if not state then return nil end
|
||||
for _, page in ipairs(state.order) do
|
||||
if code >= page.base then return page end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Convert a text string into a list of glyph codes. Unknown characters
|
||||
@@ -72,27 +136,39 @@ function Font.encode(text)
|
||||
end
|
||||
|
||||
function Font.drawCode(code, x, y)
|
||||
local def = state.def
|
||||
if code >= def.mainBase then
|
||||
love.graphics.draw(state.main, state.mainQuads[code - def.mainBase], x, y)
|
||||
elseif code >= def.extraBase then
|
||||
love.graphics.draw(state.extra, state.extraQuads[code - def.extraBase], x, y)
|
||||
end
|
||||
local page = pageFor(code)
|
||||
if not page then return end
|
||||
local quad = page.quads[code - page.base]
|
||||
if quad then love.graphics.draw(page.image, quad, x, y) end
|
||||
end
|
||||
|
||||
-- Draw a plain single-line string at pixel (x, y).
|
||||
-- how far the pen moves past a glyph; 8 unless its page says otherwise
|
||||
function Font.advanceOf(code)
|
||||
local page = pageFor(code)
|
||||
return page and page.advance or GLYPH
|
||||
end
|
||||
|
||||
-- Draw a plain single-line string at pixel (x, y). Returns the width
|
||||
-- drawn, which is #codes * 8 for every fixed-width page.
|
||||
function Font.draw(text, x, y)
|
||||
local codes = Font.encode(text)
|
||||
for i, code in ipairs(codes) do
|
||||
Font.drawCode(code, x + (i - 1) * 8, y)
|
||||
local pen = x
|
||||
for _, code in ipairs(codes) do
|
||||
Font.drawCode(code, pen, y)
|
||||
pen = pen + Font.advanceOf(code)
|
||||
end
|
||||
return #codes * 8
|
||||
return pen - x
|
||||
end
|
||||
|
||||
-- Border glyph codes (font_extra.png, from charmap.asm $79-$7E)
|
||||
Font.BORDER = {
|
||||
-- Border glyph codes (font_extra.png, from charmap.asm $79-$7E). A font
|
||||
-- that draws its boxes from different glyphs sets data.font.border and
|
||||
-- Font.load folds it over these; the table itself stays writable so a mod
|
||||
-- can retheme one corner without shipping a whole page.
|
||||
Font.DEFAULT_BORDER = {
|
||||
tl = 0x79, h = 0x7A, tr = 0x7B, v = 0x7C, bl = 0x7D, br = 0x7E,
|
||||
}
|
||||
Font.BORDER = {}
|
||||
for key, code in pairs(Font.DEFAULT_BORDER) do Font.BORDER[key] = code end
|
||||
|
||||
-- Draw a Game Boy style bordered box in tile coordinates.
|
||||
function Font.drawBox(tx, ty, tw, th)
|
||||
|
||||
Reference in New Issue
Block a user