添加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
local utf8display = {}
@@ -101,7 +101,9 @@ end
function fontManager.getFontHeight()
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]
end
return state.fontHeight
@@ -110,48 +112,6 @@ end
-- 渲染引擎模块
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)
local i = 1
return function()
@@ -210,254 +170,381 @@ function utf8display.loadFont()
return true
end
-- 主动初始化字体函数
function utf8display.initFont()
return utf8display.loadFont()
-- 将字符串转换为bimg格式完整修正版支持逐字颜色
function utf8display.strToBimg(str, textColor, backgroundColor, width)
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
local function clampCursorPos(x, y, width, height)
return math.min(math.max(x, 1), width), math.min(math.max(y, 1), height)
end
function utf8display.write(raw_str)
str = tostring(raw_str)
-- 将换行符替换为空格
str = string.gsub(str, "\n", " ")
local font = fontManager.getFont()
local fontHeight = fontManager.getFontHeight()
local textColor = term.getTextColor()
local backgroundColor = term.getBackgroundColor()
function utf8display.write(raw_str, textColor, backgroundColor)
local str = tostring(raw_str)
str = string.gsub(str, "\n", " ") -- 替换换行为空格
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 termWidth, termHeight = term.getSize()
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]
-- 获取 bimg无宽度限制所以不会自动换行
local bimg = utf8display.strToBimg(str, textColor, backgroundColor)
local frame = bimg[1]
-- 不自动换行:即使超出横向宽度也继续写(但不渲染超出部分)
if cursorX + charWidth - 1 > width then
contentFitsHorizontally = false
end
if #frame == 0 then
-- 空内容,直接返回
return true, { charCount = 0, overflowX = false, overflowY = false }
end
-- 判断是否还能在竖直方向绘制(至少第一行能画)
if cursorY <= height then
renderer.displayChar(charMap, cursorX, cursorY, textColor, backgroundColor)
charCount = charCount + 1
else
contentFitsVertically = false
-- 不 break继续推进光标逻辑用于正确设置最终光标位置
end
local firstLineWidth = #frame[1][1]
local totalHeight = #frame
cursorX = cursorX + charWidth
-- 渲染每一行
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 not contentFitsHorizontally or not contentFitsVertically then
-- 若任一方向溢出,光标回到起始位置
if verticalOverflow then
-- 情况3纵向溢出 → 回退到起点
finalX, finalY = startX, startY
elseif horizontalOverflow then
-- 情况2宽但不高 → 光标移到块下方
finalX, finalY = startX, startY + totalHeight
else
-- 否则放在正常结束位置
finalX, finalY = cursorX, cursorY
-- 情况1窄且不高 → 横向推进(仅第一行宽度)
finalX, finalY = startX + firstLineWidth, startY
end
-- 安全设置光标(避免超出屏幕导致卡住)
finalX, finalY = clampCursorPos(finalX, finalY, width, height)
finalX, finalY = clampCursorPos(finalX, finalY, termWidth, termHeight)
term.setCursorPos(finalX, finalY)
return true, {
textColor = textColor,
backgroundColor = backgroundColor,
startX = startX,
startY = startY,
endX = finalX,
endY = finalY,
charCount = charCount,
fontHeight = fontHeight,
overflowX = not contentFitsHorizontally,
overflowY = not contentFitsVertically
charCount = utf8.len(str), -- 假设有 utf8.len或可估算
fontHeight = fontManager.getFontHeight(),
overflowX = horizontalOverflow,
overflowY = verticalOverflow
}
end
function utf8display.print(raw_str)
str = tostring(raw_str)
local font = fontManager.getFont()
local fontHeight = fontManager.getFontHeight()
local textColor = term.getTextColor()
local backgroundColor = term.getBackgroundColor()
function utf8display.blit(raw_str, textColorStr, backgroundColorStr)
local str = tostring(raw_str)
str = string.gsub(str, "\n", " ")
local startX, startY = term.getCursorPos()
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 width, height = term.getSize()
-- 预计算所需总高度(用于滚动)
local tempCursorX, tempCursorY = startX, startY
for code in renderer.utf8Decode(str) do
if code == 10 then
tempCursorX = 1
tempCursorY = tempCursorY + fontHeight
else
local charMap = font[code] or font[32]
local charWidth = #charMap[1]
if tempCursorX + charWidth - 1 > width then
tempCursorX = 1
tempCursorY = tempCursorY + fontHeight
end
tempCursorX = tempCursorX + charWidth
end
-- 获取颜色
local useTextColor = textColor
local useBackgroundColor = backgroundColor
if useTextColor == nil then useTextColor = term.getTextColor() end
if useBackgroundColor == nil then useBackgroundColor = term.getBackgroundColor() end
-- 生成 bimg传入宽度以启用自动换行
local bimg = utf8display.strToBimg(str, useTextColor, useBackgroundColor, width)
local frame = bimg[1]
local totalLines = #frame
local availableLines = height - startY + 1
-- 渲染
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
local totalContentHeight = tempCursorY + fontHeight - startY
local availableHeight = height - startY + 1
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 finalY = startY + totalLines
local finalX = 1
finalX, finalY = clampCursorPos(finalX, finalY, width, height)
term.setCursorPos(finalX, finalY)
return true, {
textColor = textColor,
backgroundColor = backgroundColor,
startX = startX,
startY = startY,
endX = finalX,
endY = finalY,
charCount = charCount,
fontHeight = fontHeight
lineCount = totalLines
}
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, {
__index = function(self, key)