添加strToBimg,并让其他显示基于strToBimg

This commit is contained in:
2025-12-08 19:29:43 +08:00
parent a81c44106a
commit 78db749cb8

View File

@@ -1,4 +1,4 @@
-- UTF-8 Display Library for ComputerCraft -- UTF-8 Display Library for ComputerCraft (完整修正版)
-- 文件名: utf8display.lua -- 文件名: utf8display.lua
local utf8display = {} local utf8display = {}
@@ -101,7 +101,9 @@ end
function fontManager.getFontHeight() function fontManager.getFontHeight()
local font = fontManager.getFont() local font = fontManager.getFont()
if font and font[32] then if font and font[72] then -- 'H' (ASCII 72) 表示最大高度
return #font[72]
elseif font and font[32] then
return #font[32] return #font[32]
end end
return state.fontHeight return state.fontHeight
@@ -110,48 +112,6 @@ end
-- 渲染引擎模块 -- 渲染引擎模块
local renderer = {} local renderer = {}
function renderer.displayChar(charMap, x, y, textColor, backgroundColor)
if not charMap or #charMap == 0 then
return
end
-- 保存当前终端颜色和光标位置
local originalTextColor = term.getTextColor()
local originalBackgroundColor = term.getBackgroundColor()
local originalCursorX, originalCursorY = term.getCursorPos()
-- 如果没有提供自定义颜色,则使用当前终端颜色
local useTextColor = textColor or originalTextColor
local useBackgroundColor = backgroundColor or originalBackgroundColor
-- 渲染字符(仅在可视区域内)
local width, height = term.getSize()
for row = 1, #charMap do
local drawY = y + row - 1
if drawY > height then break end -- 超出底部,不再绘制
term.setCursorPos(x, drawY)
local line = charMap[row]
for col = 1, #line do
local byte = string.byte(line, col)
if byte < 128 then
term.setTextColor(useBackgroundColor)
term.setBackgroundColor(useTextColor)
term.write(string.char(byte + 128))
else
term.setTextColor(useTextColor)
term.setBackgroundColor(useBackgroundColor)
term.write(string.char(byte))
end
end
end
-- 恢复原始终端颜色和光标位置
term.setTextColor(originalTextColor)
term.setBackgroundColor(originalBackgroundColor)
term.setCursorPos(originalCursorX, originalCursorY)
end
function renderer.utf8Decode(str) function renderer.utf8Decode(str)
local i = 1 local i = 1
return function() return function()
@@ -210,254 +170,381 @@ function utf8display.loadFont()
return true return true
end end
-- 主动初始化字体函数 -- 将字符串转换为bimg格式完整修正版支持逐字颜色
function utf8display.initFont() function utf8display.strToBimg(str, textColor, backgroundColor, width)
return utf8display.loadFont() str = tostring(str)
local font = fontManager.getFont()
local fontHeight = fontManager.getFontHeight()
-- 判断颜色参数类型
local textIsString = type(textColor) == "string"
local bgIsString = type(backgroundColor) == "string"
-- 获取默认颜色
local defaultTextColor = term.getTextColor()
local defaultBgColor = term.getBackgroundColor()
-- 处理统一颜色(数字)或逐字颜色(字符串)
local uniformTextBlit, uniformBgBlit
if not textIsString then
uniformTextBlit = colors.toBlit(type(textColor) == "number" and textColor or defaultTextColor)
end
if not bgIsString then
uniformBgBlit = colors.toBlit(type(backgroundColor) == "number" and backgroundColor or defaultBgColor)
end
-- 初始化bimg结构
local bimg = {}
-- 处理换行符
local lines = {}
local start = 1
while start <= #str do
local nl_pos = str:find("\n", start, true)
if nl_pos then
local line = str:sub(start, nl_pos - 1)
table.insert(lines, line)
start = nl_pos + 1
else
-- 到末尾了
local line = str:sub(start)
table.insert(lines, line)
break
end
end
for _, lineStr in ipairs(lines) do
-- 收集该行所有字符信息
local allChars = {}
for code in renderer.utf8Decode(lineStr) do
local charMap = font[code] or font[32]
local charWidth = #charMap[1]
table.insert(allChars, {code = code, map = charMap, width = charWidth})
end
if width then
-- 有宽度限制,需要处理自动换行
local segments = {}
local currentSegment = {}
local currentLineWidth = 0
-- 遍历字符
for i, charInfo in ipairs(allChars) do
-- 检查是否需要换行
if currentLineWidth + charInfo.width > width then
-- 将当前段添加到segments
if #currentSegment > 0 then
table.insert(segments, currentSegment)
end
-- 开始新段
currentSegment = {}
currentLineWidth = 0
end
-- 添加字符到当前段
table.insert(currentSegment, charInfo)
currentLineWidth = currentLineWidth + charInfo.width
end
-- 添加最后一段(如果存在)
if #currentSegment > 0 then
table.insert(segments, currentSegment)
end
-- 渲染每个段(每个段高度 = fontHeight
for _, segment in ipairs(segments) do
-- 为该段构建每行的 text/colors/bg
for row = 1, fontHeight do
local lineText = ""
local lineTextColors = ""
local lineBgColors = ""
-- 为每个字符计算颜色(如果使用字符串颜色)
local charIndexInLine = 1
for _, char in ipairs(segment) do
local fgBlit, bgBlit
if textIsString then
local colorChar = string.sub(textColor, charIndexInLine, charIndexInLine)
fgBlit = colorChar ~= "" and colorChar or "f" -- 默认白色
else
fgBlit = uniformTextBlit
end
if bgIsString then
local colorChar = string.sub(backgroundColor, charIndexInLine, charIndexInLine)
bgBlit = colorChar ~= "" and colorChar or "0" -- 默认黑色
else
bgBlit = uniformBgBlit
end
-- 处理该字符的当前行
if row <= #char.map then
local rowStr = char.map[row]
for col = 1, #rowStr do
local byte = string.byte(rowStr, col)
local displayByte
if byte < 128 then
-- 颜色反转
displayByte = byte + 128
lineText = lineText .. string.char(displayByte)
lineTextColors = lineTextColors .. bgBlit -- 原背景色变前景
lineBgColors = lineBgColors .. fgBlit -- 原前景色变背景
else
-- 正常
displayByte = byte
lineText = lineText .. string.char(displayByte)
lineTextColors = lineTextColors .. fgBlit
lineBgColors = lineBgColors .. bgBlit
end
end
end
charIndexInLine = charIndexInLine + 1
end
table.insert(bimg, {lineText, lineTextColors, lineBgColors})
end
end
else
-- 无宽度限制,整行处理
if #allChars > 0 then
-- 为该行构建每行的 text/colors/bg
for row = 1, fontHeight do
local lineText = ""
local lineTextColors = ""
local lineBgColors = ""
-- 为每个字符计算颜色(如果使用字符串颜色)
local charIndexInLine = 1
for _, char in ipairs(allChars) do
local fgBlit, bgBlit
if textIsString then
local colorChar = string.sub(textColor, charIndexInLine, charIndexInLine)
fgBlit = colorChar ~= "" and colorChar or "f" -- 默认白色
else
fgBlit = uniformTextBlit
end
if bgIsString then
local colorChar = string.sub(backgroundColor, charIndexInLine, charIndexInLine)
bgBlit = colorChar ~= "" and colorChar or "0" -- 默认黑色
else
bgBlit = uniformBgBlit
end
-- 处理该字符的当前行
if row <= #char.map then
local rowStr = char.map[row]
for col = 1, #rowStr do
local byte = string.byte(rowStr, col)
local displayByte
if byte < 128 then
-- 颜色反转
displayByte = byte + 128
lineText = lineText .. string.char(displayByte)
lineTextColors = lineTextColors .. bgBlit -- 原背景色变前景
lineBgColors = lineBgColors .. fgBlit -- 原前景色变背景
else
-- 正常
displayByte = byte
lineText = lineText .. string.char(displayByte)
lineTextColors = lineTextColors .. fgBlit
lineBgColors = lineBgColors .. bgBlit
end
end
end
charIndexInLine = charIndexInLine + 1
end
table.insert(bimg, {lineText, lineTextColors, lineBgColors})
end
else
-- 空行也需要保留
table.insert(bimg, {"", "", ""})
end
end
end
-- 如果没有内容,创建一个空行
if #bimg == 0 then
table.insert(bimg, {"", "", ""})
end
-- 将所有行包装在第一帧中
return {{unpack(bimg)}}
end end
local function clampCursorPos(x, y, width, height) local function clampCursorPos(x, y, width, height)
return math.min(math.max(x, 1), width), math.min(math.max(y, 1), height) return math.min(math.max(x, 1), width), math.min(math.max(y, 1), height)
end end
function utf8display.write(raw_str) function utf8display.write(raw_str, textColor, backgroundColor)
str = tostring(raw_str) local str = tostring(raw_str)
-- 将换行符替换为空格 str = string.gsub(str, "\n", " ") -- 替换换行为空格
str = string.gsub(str, "\n", " ")
local font = fontManager.getFont()
local fontHeight = fontManager.getFontHeight()
local textColor = term.getTextColor()
local backgroundColor = term.getBackgroundColor()
local startX, startY = term.getCursorPos() local startX, startY = term.getCursorPos()
local width, height = term.getSize() local termWidth, termHeight = term.getSize()
local cursorX, cursorY = startX, startY
local charCount = 0
local contentFitsHorizontally = true
local contentFitsVertically = true
for code in renderer.utf8Decode(str) do -- 获取 bimg无宽度限制所以不会自动换行
if code == 10 then -- 换行符 (理论上不会再有,因为我们已经替换了) local bimg = utf8display.strToBimg(str, textColor, backgroundColor)
cursorX = 1 local frame = bimg[1]
cursorY = cursorY + fontHeight
else
local charMap = font[code] or font[32]
local charWidth = #charMap[1]
-- 不自动换行:即使超出横向宽度也继续写(但不渲染超出部分) if #frame == 0 then
if cursorX + charWidth - 1 > width then -- 空内容,直接返回
contentFitsHorizontally = false return true, { charCount = 0, overflowX = false, overflowY = false }
end end
-- 判断是否还能在竖直方向绘制(至少第一行能画) local firstLineWidth = #frame[1][1]
if cursorY <= height then local totalHeight = #frame
renderer.displayChar(charMap, cursorX, cursorY, textColor, backgroundColor)
charCount = charCount + 1
else
contentFitsVertically = false
-- 不 break继续推进光标逻辑用于正确设置最终光标位置
end
cursorX = cursorX + charWidth -- 渲染每一行
for i, row in ipairs(frame) do
local drawY = startY + i - 1
if drawY > termHeight then
-- 超出底部,跳过绘制
break
end end
term.setCursorPos(startX, drawY)
term.blit(row[1], row[2], row[3])
end end
-- 确定最终光标位置 -- 判断是否纵向溢出
local verticalOverflow = (startY + totalHeight - 1) > termHeight
local horizontalOverflow = firstLineWidth >= termWidth
local finalX, finalY local finalX, finalY
if not contentFitsHorizontally or not contentFitsVertically then
-- 若任一方向溢出,光标回到起始位置 if verticalOverflow then
-- 情况3纵向溢出 → 回退到起点
finalX, finalY = startX, startY finalX, finalY = startX, startY
elseif horizontalOverflow then
-- 情况2宽但不高 → 光标移到块下方
finalX, finalY = startX, startY + totalHeight
else else
-- 否则放在正常结束位置 -- 情况1窄且不高 → 横向推进(仅第一行宽度)
finalX, finalY = cursorX, cursorY finalX, finalY = startX + firstLineWidth, startY
end end
-- 安全设置光标(避免超出屏幕导致卡住) finalX, finalY = clampCursorPos(finalX, finalY, termWidth, termHeight)
finalX, finalY = clampCursorPos(finalX, finalY, width, height)
term.setCursorPos(finalX, finalY) term.setCursorPos(finalX, finalY)
return true, { return true, {
textColor = textColor,
backgroundColor = backgroundColor,
startX = startX, startX = startX,
startY = startY, startY = startY,
endX = finalX, endX = finalX,
endY = finalY, endY = finalY,
charCount = charCount, charCount = utf8.len(str), -- 假设有 utf8.len或可估算
fontHeight = fontHeight, fontHeight = fontManager.getFontHeight(),
overflowX = not contentFitsHorizontally, overflowX = horizontalOverflow,
overflowY = not contentFitsVertically overflowY = verticalOverflow
} }
end end
function utf8display.print(raw_str) function utf8display.blit(raw_str, textColorStr, backgroundColorStr)
str = tostring(raw_str) local str = tostring(raw_str)
local font = fontManager.getFont() str = string.gsub(str, "\n", " ")
local fontHeight = fontManager.getFontHeight()
local textColor = term.getTextColor() local startX, startY = term.getCursorPos()
local backgroundColor = term.getBackgroundColor() local termWidth, termHeight = term.getSize()
-- 直接传入颜色字符串
local bimg = utf8display.strToBimg(str, textColorStr, backgroundColorStr)
local frame = bimg[1]
if #frame == 0 then
return true, { charCount = 0, overflowX = false, overflowY = false }
end
local firstLineWidth = #frame[1][1]
local totalHeight = #frame
-- 渲染
for i, row in ipairs(frame) do
local drawY = startY + i - 1
if drawY > termHeight then
break
end
term.setCursorPos(startX, drawY)
term.blit(row[1], row[2], row[3])
end
local verticalOverflow = (startY + totalHeight - 1) > termHeight
local horizontalOverflow = firstLineWidth >= termWidth
local finalX, finalY
if verticalOverflow then
finalX, finalY = startX, startY
elseif horizontalOverflow then
finalX, finalY = startX, startY + totalHeight
else
finalX, finalY = startX + firstLineWidth, startY
end
finalX, finalY = clampCursorPos(finalX, finalY, termWidth, termHeight)
term.setCursorPos(finalX, finalY)
return true, {
startX = startX,
startY = startY,
endX = finalX,
endY = finalY,
charCount = utf8.len(str),
fontHeight = fontManager.getFontHeight(),
overflowX = horizontalOverflow,
overflowY = verticalOverflow
}
end
function utf8display.print(raw_str, textColor, backgroundColor)
local str = tostring(raw_str)
local startX, startY = term.getCursorPos() local startX, startY = term.getCursorPos()
local width, height = term.getSize() local width, height = term.getSize()
-- 预计算所需总高度(用于滚动) -- 获取颜色
local tempCursorX, tempCursorY = startX, startY local useTextColor = textColor
for code in renderer.utf8Decode(str) do local useBackgroundColor = backgroundColor
if code == 10 then if useTextColor == nil then useTextColor = term.getTextColor() end
tempCursorX = 1 if useBackgroundColor == nil then useBackgroundColor = term.getBackgroundColor() end
tempCursorY = tempCursorY + fontHeight
else -- 生成 bimg传入宽度以启用自动换行
local charMap = font[code] or font[32] local bimg = utf8display.strToBimg(str, useTextColor, useBackgroundColor, width)
local charWidth = #charMap[1] local frame = bimg[1]
if tempCursorX + charWidth - 1 > width then
tempCursorX = 1 local totalLines = #frame
tempCursorY = tempCursorY + fontHeight local availableLines = height - startY + 1
end
tempCursorX = tempCursorX + charWidth
end -- 渲染
for i, rowData in ipairs(frame) do
local drawY = startY + i - 1
if drawY >= height then term.setCursorPos(1, height) else term.setCursorPos(1, drawY) end
term.blit(rowData[1], rowData[2], rowData[3])
if drawY >= height then term.scroll(1) end
end end
local totalContentHeight = tempCursorY + fontHeight - startY -- 光标移到下一行开头
local availableHeight = height - startY + 1 local finalY = startY + totalLines
local scrollHeight = math.max(0, totalContentHeight - availableHeight)
if scrollHeight > 0 and utf8display.config.autoScroll then
term.scroll(scrollHeight)
startY = startY - scrollHeight
end
-- 实际渲染
local cursorX, cursorY = startX, startY
local charCount = 0
for code in renderer.utf8Decode(str) do
if code == 10 then
cursorX = 1
cursorY = cursorY + fontHeight
else
local charMap = font[code] or font[32]
local charWidth = #charMap[1]
if cursorX + charWidth - 1 > width then
cursorX = 1
cursorY = cursorY + fontHeight
end
if cursorY <= height then
renderer.displayChar(charMap, cursorX, cursorY, textColor, backgroundColor)
charCount = charCount + 1
end
cursorX = cursorX + charWidth
end
end
-- print 总是把光标放到下一行开头(符合常规行为)
local finalY = cursorY + fontHeight
local finalX = 1 local finalX = 1
finalX, finalY = clampCursorPos(finalX, finalY, width, height) finalX, finalY = clampCursorPos(finalX, finalY, width, height)
term.setCursorPos(finalX, finalY) term.setCursorPos(finalX, finalY)
return true, { return true, {
textColor = textColor,
backgroundColor = backgroundColor,
startX = startX, startX = startX,
startY = startY, startY = startY,
endX = finalX, endX = finalX,
endY = finalY, endY = finalY,
charCount = charCount, lineCount = totalLines
fontHeight = fontHeight
} }
end end
function utf8display.blit(raw_str, textColorStr, backgroundColorStr)
text = tostring(raw_str)
-- 将换行符替换为空格
text = string.gsub(text, "\n", " ")
local font = fontManager.getFont()
local fontHeight = fontManager.getFontHeight()
local startX, startY = term.getCursorPos()
local width, height = term.getSize()
local cursorX, cursorY = startX, startY
local charCount = 0
local contentFitsHorizontally = true
local contentFitsVertically = true
-- 解析颜色字符串
local textColors = {}
local backgroundColors = {}
for i = 1, #textColorStr do
textColors[i] = colors.fromBlit(string.sub(textColorStr, i, i))
end
for i = 1, #backgroundColorStr do
backgroundColors[i] = colors.fromBlit(string.sub(backgroundColorStr, i, i))
end
local index = 1
for code in renderer.utf8Decode(text) do
if code == 10 then -- 换行符 (理论上不会再有,因为我们已经替换了)
cursorX = 1
cursorY = cursorY + fontHeight
else
local charMap = font[code] or font[32]
local charWidth = #charMap[1]
if cursorX + charWidth - 1 > width then
contentFitsHorizontally = false
end
if cursorY <= height then
local useTextColor = textColors[index] or term.getTextColor()
local useBackgroundColor = backgroundColors[index] or term.getBackgroundColor()
renderer.displayChar(charMap, cursorX, cursorY, useTextColor, useBackgroundColor)
charCount = charCount + 1
else
contentFitsVertically = false
end
cursorX = cursorX + charWidth
index = index + 1
end
end
local finalX, finalY
if not contentFitsHorizontally or not contentFitsVertically then
finalX, finalY = startX, startY
else
finalX, finalY = cursorX, cursorY
end
finalX, finalY = clampCursorPos(finalX, finalY, width, height)
term.setCursorPos(finalX, finalY)
return true, {
startX = startX,
startY = startY,
endX = finalX,
endY = finalY,
charCount = charCount,
fontHeight = fontHeight,
overflowX = not contentFitsHorizontally,
overflowY = not contentFitsVertically
}
end
-- 工具函数
function utf8display.getFontInfo()
local font = fontManager.getFont()
local fontHeight = fontManager.getFontHeight()
return {
height = fontHeight,
loaded = font ~= nil,
source = utf8display.config.fontPath or utf8display.config.fontUrl,
cached = state.loadedFonts[utf8display.config.fontPath or utf8display.config.fontUrl] ~= nil
}
end
function utf8display.isInitialized()
return state.font ~= nil
end
-- 自动初始化(第一次使用时) -- 自动初始化(第一次使用时)
setmetatable(utf8display, { setmetatable(utf8display, {
__index = function(self, key) __index = function(self, key)