Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| da070bc9dd | |||
| 78db749cb8 | |||
| a81c44106a | |||
| 8f32e8b3d6 | |||
| e4bb015e77 | |||
| 765d760177 | |||
| 0f95039951 | |||
| 87ea387834 |
210
utf8display/README.md
Normal file
210
utf8display/README.md
Normal file
@@ -0,0 +1,210 @@
|
||||
# UTF-8 Display Library for ComputerCraft
|
||||
|
||||
> 一个支持中文与 UTF-8 字符的高性能像素字体显示库,适用于 **ComputerCraft**(包括 CC:Tweaked)环境。
|
||||
|
||||
---
|
||||
|
||||
## 📦 安装
|
||||
|
||||
### 方法一:本地加载(推荐)
|
||||
#### 下载到本地
|
||||
```shell
|
||||
wget https://git.liulikeji.cn/xingluo/ComputerCraft-Utf8/raw/branch/main/utf8display/utf8display.lua utf8display.lua
|
||||
```
|
||||
#### 在 Lua 脚本中使用
|
||||
```lua
|
||||
local utf8display = require("utf8display")
|
||||
```
|
||||
|
||||
### 方法二:远程加载(无需存储)
|
||||
|
||||
```lua
|
||||
local utf8display = load(http.get("https://git.liulikeji.cn/xingluo/ComputerCraft-Utf8/raw/branch/main/utf8display/utf8display.lua").readAll())()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ 配置
|
||||
|
||||
默认配置如下:
|
||||
|
||||
```lua
|
||||
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, -- 是否缓存已加载字体(提升性能)
|
||||
}
|
||||
```
|
||||
|
||||
### 修改配置
|
||||
|
||||
```lua
|
||||
-- 示例:使用本地字体
|
||||
utf8display.setConfig("fontPath", "/fonts/my_font.lua")
|
||||
|
||||
-- 示例:更换远程字体
|
||||
utf8display.setConfig("fontUrl", "https://example.com/my_font.lua")
|
||||
```
|
||||
|
||||
> ✅ 支持的配置项:`fontUrl`、`fontPath`、`cacheFont`
|
||||
|
||||
---
|
||||
|
||||
## 🧰 主要函数
|
||||
|
||||
### `utf8display.write(str, [textColor, backgroundColor])`
|
||||
- **用途**:不自动换行地输出文本(适合标题、状态栏等)
|
||||
- **行为**:
|
||||
- 换行符 `\n` 会被替换为空格
|
||||
- 若内容超出屏幕,光标回退至起始位置
|
||||
- 支持统一颜色或逐字颜色(见下文"颜色控制")
|
||||
- **返回值**:`{success, info}`
|
||||
|
||||
### `utf8display.print(str, [textColor, backgroundColor])`
|
||||
- **用途**:自动换行输出文本(类似传统 `print`)
|
||||
- **行为**:
|
||||
- 正常处理 `\n` 换行
|
||||
- 根据终端宽度自动换行
|
||||
- 光标最终位于输出内容**下一行开头**
|
||||
- **返回值**:`{success, info}`
|
||||
|
||||
### `utf8display.blit(str, textColorStr, backgroundColorStr)`
|
||||
- **用途**:高级颜色控制输出(逐字符指定颜色)
|
||||
- **参数**:
|
||||
- `textColorStr`:前景色字符串(如 `"123456"`,每个字符对应一个 blit 颜色码)
|
||||
- `backgroundColorStr`:背景色字符串(同上)
|
||||
- **注意**:`\n` 同样被替换为空格
|
||||
- **返回值**:`{success, info}`
|
||||
|
||||
### `utf8display.strToBimg(str, textColor, backgroundColor, [width])`
|
||||
- **参数说明**:
|
||||
- `str`:要转换的字符串
|
||||
- `textColor`:前景色,可以是:
|
||||
- `colors.xxx`(如 `colors.red`)
|
||||
- blit颜色字符串(如 `"fff"`)
|
||||
- `backgroundColor`:背景色,可以是:
|
||||
- `colors.xxx`(如 `colors.black`)
|
||||
- blit颜色字符串(如 `"000"`)
|
||||
- `[width]`:行宽限制(可选),如果输入数字则根据此大小进行自动换行
|
||||
- **用途**:将字符串转换为 `bimg` 格式(用于自定义渲染)
|
||||
- **返回值**:符合 `term.blit()` 格式的多帧图像结构(当前仅单帧)
|
||||
|
||||
### `utf8display.loadFont()`
|
||||
- **用途**:手动加载字体资源
|
||||
- **行为**:根据配置加载远程或本地字体
|
||||
- **返回值**:`{success, error_message}`
|
||||
|
||||
---
|
||||
|
||||
## 🎨 颜色控制
|
||||
|
||||
### 统一颜色(数字)
|
||||
|
||||
```lua
|
||||
utf8display.write("Hello 世界!", colors.red, colors.black)
|
||||
```
|
||||
|
||||
### 逐字颜色(字符串)
|
||||
|
||||
```lua
|
||||
utf8display.blit("ABC", "123", "000")
|
||||
```
|
||||
|
||||
> 💡 颜色字符串长度不足时,后续字符使用默认色
|
||||
|
||||
---
|
||||
|
||||
## 📝 使用示例
|
||||
|
||||
### 基础用法
|
||||
|
||||
```lua
|
||||
local utf8display = require("utf8display")
|
||||
|
||||
-- 自动初始化字体(首次调用 write/print 时触发)
|
||||
utf8display.write("你好,ComputerCraft!")
|
||||
|
||||
utf8display.print("这是一段\n包含换行的\n中文文本。")
|
||||
```
|
||||
|
||||
### 彩色输出
|
||||
|
||||
```lua
|
||||
-- 统一颜色
|
||||
utf8display.write("警告!", colors.orange, colors.black)
|
||||
|
||||
-- 逐字变色(彩虹效果)
|
||||
utf8display.blit("RAINBOW", "1234567", "0000000")
|
||||
```
|
||||
|
||||
### 使用本地字体
|
||||
|
||||
```lua
|
||||
utf8display.setConfig("fontPath", "/my_fonts/chinese_font.lua")
|
||||
utf8display.write("使用本地字体!")
|
||||
```
|
||||
|
||||
### 手动加载字体
|
||||
|
||||
```lua
|
||||
local success, error_msg = utf8display.loadFont()
|
||||
if success then
|
||||
print("字体加载成功!")
|
||||
else
|
||||
print("字体加载失败:" .. error_msg)
|
||||
end
|
||||
```
|
||||
|
||||
### 自动换行功能
|
||||
|
||||
```lua
|
||||
-- 使用strToBimg的宽度参数进行自动换行
|
||||
local bimg = utf8display.strToBimg("这是一段很长的文本,需要自动换行显示", colors.white, colors.black, 20)
|
||||
-- 文本将在第20个cc字符位置自动换行
|
||||
|
||||
-- 或者在print中自动换行
|
||||
utf8display.print("这是一段很长的文本,会自动换行显示", colors.white, colors.black)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔙 返回值说明
|
||||
|
||||
所有显示函数均返回以下结构:
|
||||
|
||||
```lua
|
||||
{
|
||||
success = true,
|
||||
startX = 1, -- 起始光标 X
|
||||
startY = 1, -- 起始光标 Y
|
||||
endX = 10, -- 结束光标 X
|
||||
endY = 2, -- 结束光标 Y
|
||||
charCount = 5, -- 显示的字符数(UTF-8 计数)
|
||||
fontHeight = 8, -- 字体高度(行高)
|
||||
overflowX = false, -- 水平溢出(write/blit)
|
||||
overflowY = false, -- 垂直溢出(write/blit)
|
||||
lineCount = 3 -- 行数(仅 print)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
1. **换行行为**:
|
||||
- `write` / `blit`:`\n` → 空格
|
||||
- `print`:`\n` → 实际换行 + 自动折行
|
||||
2. **性能**:
|
||||
- 首次使用会自动加载字体(网络或本地)
|
||||
- 启用 `cacheFont = true` 可避免重复加载
|
||||
3. **渲染机制**:
|
||||
- 使用 `term.blit()` 高效绘制
|
||||
|
||||
---
|
||||
|
||||
## 🛠 如何制作自定义字体
|
||||
|
||||
- 字体文件返回一个table,键值为utf8编码,值为和对应字体的bitmap。
|
||||
- bitmap为一个包含等长string的table,string中的char属于computer craft定义的2*3像素点阵(如需使用右下角像素,将char的码值减128表示反转backgroundColor 和 textColor)
|
||||
- 单个字体文件中可以有不同尺寸的bitmap,且**需要**有'H'(ascII:72)的bitmap表示该文件中最大bitmap高度
|
||||
- 会以FontFamily出现的最大bitmap高度为基准,最终输出下对齐的文本
|
||||
- FontFamily中**需要**'-'(ascII:45)的bitmap以供自动换行时可能的切断单词使用
|
||||
564
utf8display/utf8display.lua
Normal file
564
utf8display/utf8display.lua
Normal file
@@ -0,0 +1,564 @@
|
||||
-- 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
|
||||
209
utf8print12px.lua
Normal file
209
utf8print12px.lua
Normal file
@@ -0,0 +1,209 @@
|
||||
--这是一个简单的打印字符的程序,他使用下方url的字体进行显示
|
||||
--因为字体文件超过cc存储大小所以使用网络加载字体
|
||||
|
||||
--通过printUtf8("字符",文字颜色,背景颜色)来达到类似 print的效果
|
||||
--示例:printUtf8("你好世界! Hello Word!",colors.white,colors.lightGray)
|
||||
|
||||
|
||||
|
||||
|
||||
-- 从网络加载字库
|
||||
local function loadRemoteFont(url)
|
||||
local response = http.get(url)
|
||||
if not response then
|
||||
error("无法连接到字体服务器")
|
||||
end
|
||||
|
||||
if response.getResponseCode() ~= 200 then
|
||||
error("字体服务器返回错误: " .. response.getResponseCode())
|
||||
end
|
||||
|
||||
local content = response.readAll()
|
||||
response.close()
|
||||
|
||||
-- 使用沙箱环境安全加载字体
|
||||
local sandbox = {}
|
||||
local chunk, err = load(content, "=remoteFont", "t", sandbox)
|
||||
if not chunk then
|
||||
error("加载字体失败: " .. err)
|
||||
end
|
||||
|
||||
local success, result = pcall(chunk)
|
||||
if not success then
|
||||
error("执行字体脚本失败: " .. result)
|
||||
end
|
||||
|
||||
return sandbox.font or sandbox[1] or result
|
||||
end
|
||||
|
||||
-- 字体URL
|
||||
local fontUrl = "https://git.liulikeji.cn/xingluo/ComputerCraft-Utf8/raw/branch/main/fonts/fusion-pixel-12px-proportional-zh_hans.lua"
|
||||
local font = loadRemoteFont(fontUrl)
|
||||
|
||||
-- 显示单个字符的函数
|
||||
local function displayChar(charMap, x, y, textColor, backgroundColor)
|
||||
-- 保存原始终端颜色设置
|
||||
local origTextColor = term.getTextColor()
|
||||
local origBackgroundColor = term.getBackgroundColor()
|
||||
|
||||
-- 设置新颜色
|
||||
term.setTextColor(textColor)
|
||||
term.setBackgroundColor(backgroundColor)
|
||||
|
||||
-- 遍历字符位图的每一行
|
||||
for row = 1, #charMap do
|
||||
term.setCursorPos(x, y + row - 1)
|
||||
local line = charMap[row]
|
||||
|
||||
-- 遍历行中的每个像素
|
||||
for col = 1, #line do
|
||||
local byte = string.byte(line, col)
|
||||
|
||||
-- 处理像素颜色
|
||||
if byte < 128 then
|
||||
-- 背景色像素:反转颜色设置
|
||||
term.setTextColor(backgroundColor)
|
||||
term.setBackgroundColor(textColor)
|
||||
term.write(string.char(byte + 128)) -- 转换为可打印字符
|
||||
else
|
||||
-- 前景色像素:正常颜色设置
|
||||
term.setTextColor(textColor)
|
||||
term.setBackgroundColor(backgroundColor)
|
||||
term.write(string.char(byte))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 恢复原始颜色设置
|
||||
term.setTextColor(origTextColor)
|
||||
term.setBackgroundColor(origBackgroundColor)
|
||||
end
|
||||
|
||||
-- 显示UTF-8字符串的函数
|
||||
local function displayUtf8String(str, font, x, y, textColor, backgroundColor)
|
||||
-- UTF-8解码器(简化版)
|
||||
local function utf8codes(str)
|
||||
local i = 1
|
||||
return function()
|
||||
if i > #str then return end
|
||||
|
||||
local b1 = string.byte(str, i)
|
||||
i = i + 1
|
||||
|
||||
-- 单字节字符 (ASCII)
|
||||
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
|
||||
|
||||
local cursorX = x
|
||||
|
||||
-- 遍历字符串中的所有字符
|
||||
for code in utf8codes(str) do
|
||||
-- 获取字符位图
|
||||
local charMap = font[code]
|
||||
if not charMap then
|
||||
-- 如果字库中没有该字符,使用空格代替
|
||||
charMap = font[32] or {{"\x80"}} -- 空格字符
|
||||
end
|
||||
|
||||
-- 显示字符
|
||||
displayChar(charMap, cursorX, y, textColor, backgroundColor)
|
||||
|
||||
-- 移动到下一个字符位置
|
||||
cursorX = cursorX + #charMap[1]
|
||||
end
|
||||
end
|
||||
local cursorX, cursorY = 1, 1
|
||||
local fontHeight = #font[32] -- 获取字体高度(使用空格字符)
|
||||
|
||||
-- 自定义打印函数(带自动换行和滚动)
|
||||
local function printUtf8(str, textColor, backgroundColor)
|
||||
local width, screenHeight = term.getSize() -- 获取终端尺寸
|
||||
|
||||
-- UTF-8解码器
|
||||
local function utf8codes(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
|
||||
|
||||
-- 处理字符串中的每个字符
|
||||
for code in utf8codes(str) do
|
||||
-- 处理换行符
|
||||
if code == 10 then -- \n 的 ASCII
|
||||
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 + fontHeight - 1 > screenHeight then
|
||||
term.scroll(fontHeight)
|
||||
cursorY = cursorY - fontHeight
|
||||
end
|
||||
|
||||
-- 显示字符
|
||||
displayChar(charMap, cursorX, cursorY, textColor, backgroundColor)
|
||||
cursorX = cursorX + charWidth
|
||||
end
|
||||
end
|
||||
|
||||
-- 自动换行(处理完字符串后)
|
||||
cursorX = 1
|
||||
-- 光标应该位于下一行的顶部,而不是底部
|
||||
cursorY = cursorY + fontHeight
|
||||
|
||||
-- 检查滚动(换行后)
|
||||
if cursorY > screenHeight then
|
||||
term.scroll(fontHeight)
|
||||
cursorY = screenHeight - fontHeight + 1
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return printUtf8
|
||||
|
||||
Reference in New Issue
Block a user