-- UTF-8 Display Library for ComputerCraft (完整修正版) -- 文件名: utf8display.lua local utf8display = {} -- 配置参数 utf8display.config = { fontUrl = "https://git.liulikeji.cn/xingluo/ComputerCraft-Utf8/raw/branch/main/fonts/fusion-pixel-8px-proportional-zh_hans.lua", fontPath = nil, cacheFont = true, autoScroll = true } -- 内部状态 local state = { font = nil, fontHeight = 8, loadedFonts = {} } -- 字体管理模块 local fontManager = {} function fontManager.loadRemoteFont(url) if state.loadedFonts[url] then return state.loadedFonts[url] end local response = http.get(url) if not response then return nil, "无法连接到字体服务器: " .. url end if response.getResponseCode() ~= 200 then return nil, "字体服务器返回错误: " .. response.getResponseCode() end local content = response.readAll() response.close() local sandbox = {} local chunk, err = load(content, "=remoteFont", "t", sandbox) if not chunk then return nil, "加载字体失败: " .. err end local success, result = pcall(chunk) if not success then return nil, "执行字体脚本失败: " .. result end local fontData = sandbox.font or sandbox[1] or result if utf8display.config.cacheFont then state.loadedFonts[url] = fontData end return fontData end function fontManager.loadLocalFont(path) if state.loadedFonts[path] then return state.loadedFonts[path] end if not fs.exists(path) then return nil, "字体文件不存在: " .. path end local file = fs.open(path, "r") local content = file.readAll() file.close() local sandbox = {} local chunk, err = load(content, "=localFont", "t", sandbox) if not chunk then return nil, "加载本地字体失败: " .. err end local success, result = pcall(chunk) if not success then return nil, "执行本地字体脚本失败: " .. result end local fontData = sandbox.font or sandbox[1] or result if utf8display.config.cacheFont then state.loadedFonts[path] = fontData end return fontData end function fontManager.getFont() if not state.font then local success, err = utf8display.loadFont() if not success then error("字体加载失败: " .. err) end end return state.font end function fontManager.getFontHeight() local font = fontManager.getFont() 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 end -- 渲染引擎模块 local renderer = {} function renderer.utf8Decode(str) local i = 1 return function() if i > #str then return end local b1 = string.byte(str, i) i = i + 1 if b1 < 0x80 then return b1 elseif b1 >= 0xC0 and b1 < 0xE0 then local b2 = string.byte(str, i) or 0 i = i + 1 return (b1 - 0xC0) * 64 + (b2 - 0x80) elseif b1 >= 0xE0 and b1 < 0xF0 then local b2 = string.byte(str, i) or 0 i = i + 1 local b3 = string.byte(str, i) or 0 i = i + 1 return (b1 - 0xE0) * 4096 + (b2 - 0x80) * 64 + (b3 - 0x80) else return 32 end end end -- 公共API函数 function utf8display.setConfig(key, value) if utf8display.config[key] ~= nil then utf8display.config[key] = value return true end return false, "配置项不存在" end function utf8display.getConfig(key) if utf8display.config[key] ~= nil then return utf8display.config[key] end return nil, "配置项不存在" end function utf8display.loadFont() if utf8display.config.fontPath then state.font, err = fontManager.loadLocalFont(utf8display.config.fontPath) if not state.font then return false, err end else state.font, err = fontManager.loadRemoteFont(utf8display.config.fontUrl) if not state.font then return false, err end end state.fontHeight = fontManager.getFontHeight() return true end -- 将字符串转换为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, textColor, backgroundColor) local str = tostring(raw_str) str = string.gsub(str, "\n", " ") -- 替换换行为空格 local startX, startY = term.getCursorPos() local termWidth, termHeight = term.getSize() -- 获取 bimg(无宽度限制,所以不会自动换行) local bimg = utf8display.strToBimg(str, textColor, backgroundColor) 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 -- 情况3:纵向溢出 → 回退到起点 finalX, finalY = startX, startY elseif horizontalOverflow then -- 情况2:宽但不高 → 光标移到块下方 finalX, finalY = startX, startY + totalHeight else -- 情况1:窄且不高 → 横向推进(仅第一行宽度) 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), -- 假设有 utf8.len,或可估算 fontHeight = fontManager.getFontHeight(), overflowX = horizontalOverflow, overflowY = verticalOverflow } end 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 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 finalY = startY + totalLines local finalX = 1 finalX, finalY = clampCursorPos(finalX, finalY, width, height) term.setCursorPos(finalX, finalY) return true, { startX = startX, startY = startY, endX = finalX, endY = finalY, lineCount = totalLines } end -- 自动初始化(第一次使用时) setmetatable(utf8display, { __index = function(self, key) if key == "write" or key == "print" or key == "blit" then if not utf8display.isInitialized() then local success, err = utf8display.loadFont() if not success then error("自动初始化失败: " .. err) end end return rawget(self, key) end return rawget(self, key) end }) return utf8display