Add files via upload

more fonts, and a python font generation program will be uploaded in a few days
This commit is contained in:
AAAB60
2025-02-11 18:14:44 +08:00
committed by GitHub
parent 5fbc4d6df4
commit af9de2daf0
14 changed files with 1492980 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
<a href=#en>English</a> <a href=#zh>中文</a>
<div id="en"></div>
## Introduction
A pixel-based UTF-8 printing program
![example](.imgs/image1.png)
> $\color{red} \bf Note:$
> The CC compiler reads UTF-8 characters as '?',
> `print("Hello World")` has the same effect as `print("????")`.
> To fix this, read files in `"rb"` mode for proper input handling.
## How to Create Fonts
- Font files should return a table where keys are UTF-8 code points, and values are corresponding character bitmaps.
- Each bitmap is a table of equal-length strings representing a 2x3 pixel grid defined by Computer Craft. To use the bottom-right pixel, subtract 128 from the char code to invert `backgroundColor` and `textColor`.
- A single font file may contain bitmaps of different sizes, but it **must** include a bitmap for 'H' (ASCII:72) to indicate the maximum height of the font.
- The final output uses the maximum bitmap height in the FontFamily as the baseline for bottom-aligned text.
- The FontFamily **must** include a '-' (ASCII:45) bitmap for potential word splitting during auto-wrapping.
## Technical Details
- `require` a fusion-pixel-12px font consumes approximately **10.5MB** of memory.
- `require` a fusion-pixel-8px font consumes approximately **6MB** of memory.
- 8px characters occupy 3 rows vertically, while 12px characters occupy 4 rows.
- Fonts are sourced from <a href="https://github.com/TakWolf/fusion-pixel-font/releases">fusion-pixel-font</a>.
<div id="zh"></div>
## 介绍
一个基于像素打印的utf8打印程序
![example](.imgs/image1.png)
> $\color{red} \bf 注意:$
> CC 编译器会把utf8字符读作 '?',
> `print("你好世界")` 与 `print("????")` 的效果相同, 可以`"rb"`模式读取文件以设置输入
## 如何制作Font
- 字体文件返回一个table键值为utf8编码值为和对应字体的bitmap。
- bitmap为一个包含等长string的tablestring中的char属于computer craft定义的2*3像素点阵如需使用右下角像素将char的码值减128表示反转backgroundColor 和 textColor
- 单个字体文件中可以有不同尺寸的bitmap且**需要**有'H'(ascII:72)的bitmap表示该文件中最大bitmap高度
- 会以FontFamily出现的最大bitmap高度为基准最终输出下对齐的文本
- FontFamily中**需要**'-'(ascII:45)的bitmap以供自动换行时可能的切断单词使用
## 技术细节
- `require`一个fusion-pixel-12px字体大约消耗**10.5MB**内存
- `require`一个fusion-pixel-8px字体大约消耗**6MB**内存
- 8px字符实际占用3格高的字符数, 12px字符实际占用4格高
- 字体来自 <a href="https://github.com/TakWolf/fusion-pixel-font/releases"> fusion-pixel-font </a>

View File

@@ -0,0 +1,74 @@
---@module "cctAPI"
local monitor = peripheral.find("monitor")
local computer = term.redirect(monitor)
-- suggested
monitor.setTextScale(0.5)
term.setBackgroundColor(colors.black)
term.setTextColor(colors.white)
term.clear()
term.setCursorPos(1, 1)
local function pirntBorderNum()
local termWidth, termHeight = term.getSize()
local n = math.floor((termWidth - 1) / 10) + 1
local str = "0123456789"
local tc = string.rep("48", 5)
local bc = string.rep("84", 5)
term.setCursorPos(1, 1)
for i = 1, n do
term.blit(str, tc, bc)
end
local num = 1
local colorToggle = false
term.setCursorPos(1, 2)
for y = 2, termHeight do
term.setCursorPos(1, y)
if colorToggle then
term.setBackgroundColor(colors.lightGray)
term.setTextColor(colors.yellow)
colorToggle = false
else
term.setBackgroundColor(colors.yellow)
term.setTextColor(colors.lightGray)
colorToggle = true
end
term.write(tostring(num))
num = num + 1
if num == 10 then
num = 0
end
end
term.setCursorPos(2, 2)
term.setBackgroundColor(colors.black)
term.setTextColor(colors.white)
end
pirntBorderNum()
local M = require("utf8textutils")
-- NOTION:
-- CC compiler reads utf8 code as '?'
-- "print("你好世界")" will act the same as "print("????")"
-- read file in "rb" mode instead
local file = fs.open("text", "rb")
M.printUtf8(file.readLine())
local cfg1 = M.getCfg()
cfg1.fontFamily = M.getFontFamily("fonts/fusion-pixel-8px-proportional-zh_hans")
cfg1.textColor = colors.blue
cfg1.backgroundColor = colors.green
cfg1.autoWrapMode = "b"
M.printUtf8(M.sub(file.readLine(), 3, -1), cfg1)
local samplestr = "abcdefghijklmnopqrstuvwxyz"
local samplestr2 = samplestr:upper()
local samplestr1 = "0123456789"
cfg1.fontFamily = M.getFontFamily("fonts/fusion-pixel-12px-proportional-zh_hans")
--only if no utf8 character in str
M.printUtf8(samplestr, cfg1)
M.printUtf8(samplestr2, cfg1)
M.printUtf8(samplestr1, cfg1)

View File

@@ -0,0 +1,49 @@
---@module "cctAPI"
local monitor = peripheral.find("monitor")
local computer = term.redirect(monitor)
-- suggested
monitor.setTextScale(0.5)
term.clear()
term.setCursorPos(1, 1)
local M = require("utf8textutils")
---@param str string
---@param fps number films per second
local function pirntScrolling(str, fps)
str = str .. " "
local termWidth = term.getSize()
local cfg1 = M.getCfg("noauto")
cfg1.masking = { 2, 2, termWidth - 1, cfg1.fontFamily.maxHeight + 1 }
local spf = 1 / fps
---@type FontFamily
local fontFamily = cfg1.fontFamily
local wid = 0
for _, code in M.codes(str) do
local bm = M.getCharMap(code, fontFamily)
wid = wid + #bm[1]
end
local cursorX = 2
local cursorY = 2
local maskingWidth = cfg1.masking[3] - cfg1.masking[1]
local repeatNum = math.max(maskingWidth, wid)
while true do
cursorX = 2
for i = 1, repeatNum do
term.setCursorPos(cursorX, cursorY)
M.printUtf8(str, cfg1)
term.setCursorPos(cursorX + repeatNum, cursorY)
M.printUtf8(str, cfg1)
cursorX = cursorX - 1
os.sleep(spf)
end
end
end
pirntScrolling("hello -- by AAAB60", 5)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,494 @@
---@module "cctAPI"
---example:
---```
---for pos, code in codes("你好world") do
--- print(pos, code)
---end
------>
---1 20320
---4 22909
---7 119
---8 111
---9 114
---10 108
---11 100
---```
---@param str string
---@return fun():pos:integer, code:integer
local function codes(str)
local len = #str
local i = 0
local function illegalChar()
error("Illegal UTF-8 character at position " .. tostring(i))
end
return function()
i = i + 1
---@diagnostic disable-next-line
if i > len then return end
local pos = i
local byte = string.byte(str, i)
-- Single-byte character
if byte < 0x80 then
return pos, byte
-- Multi-byte sequences
elseif byte >= 0xC0 then
local bytes_remaining, code = 0, 0
if byte < 0xE0 then -- 2-byte sequence
bytes_remaining = 1
code = byte - 0xC0
elseif byte < 0xF0 then -- 3-byte sequence
bytes_remaining = 2
code = byte - 0xE0
elseif byte < 0xF8 then -- 4-byte sequence
bytes_remaining = 3
code = byte - 0xF0
else
illegalChar()
end
-- Validate remaining bytes
if i + bytes_remaining > len then
illegalChar()
end
-- Calculate code point
for j = 1, bytes_remaining do
i = i + 1
local next_byte = string.byte(str, i)
if next_byte < 0x80 or next_byte >= 0xC0 then
illegalChar()
end
code = code * 0x40 + (next_byte - 0x80)
end
return pos, code
else
illegalChar()
---@diagnostic disable-next-line
end
end
end
---@type FontFamily
local defaultFontFamily = {
maxHeight = 4,
[1] = require("fonts/fusion-pixel-12px-proportional-zh_hans")
}
---@param str string read in `"rb"` mode from file
---@param nStartPos integer? 1~n, -n~-1 to represent pos reverse
---@param nEndPos integer? 1~n, -n~-1 to represent pos reverse
---@return string substring sub string of utf8 character from `nStartPos` to `nEndPos`
local function sub(str, nStartPos, nEndPos)
local charPos = 0
local utf8charPosToStr = {}
for pos, _ in codes(str) do
charPos = charPos + 1
utf8charPosToStr[charPos] = pos
end
nStartPos = nStartPos or 1
nEndPos = nEndPos or charPos
nStartPos = nStartPos < 0 and math.max(1, charPos + nStartPos + 1) or math.min(charPos, nStartPos)
nEndPos = nEndPos < 0 and math.max(1, charPos + nEndPos + 1) or math.min(charPos, nEndPos)
local startByte = utf8charPosToStr[nStartPos]
local endByte = utf8charPosToStr[nEndPos + 1] and utf8charPosToStr[nEndPos + 1] - 1 or #str
return string.sub(str, startByte, endByte)
end
---@class FontFamily
---@field maxHeight integer
---@diagnostic disable-next-line
---@field [integer] Font get from `require(font_name)`
---@param ... string module names of font, font should be
---@return FontFamily
local function getFontFamily(...)
local fonts = { maxHeight = 0 }
for i, path in ipairs({ ... }) do
if not fs.exists(path .. ".lua") then
error("Font module not found: " .. path)
end
local font = require(path)
if not font[72] then
error("'H'(ascII:72) not found in font " .. path)
end
fonts[i] = font
fonts.maxHeight = math.max(fonts.maxHeight, #font[72])
end
return fonts
end
---@alias bitmap string[] the bitmap of a character
---@param code integer
---@param fontFamily FontFamily
---@return bitmap
local function getCharMap(code, fontFamily)
local cm
for _, font in ipairs(fontFamily) do
cm = font[code]
if cm then
return cm
end
end
error(("char of utf8 %d is not supported"):format(code))
end
---@class Config
---@field fontFamily FontFamily?
---@field textColor number?
---@field backgroundColor number?
---@field masking [integer, integer, integer, integer]?
---@field autoScroll boolean
---@field autoNewLine boolean
---@field autoWrapMode "n"|"b"|"-"?
---@field autoWrapLen integer?
---@field avoidBorder boolean
---@field tabLen integer?
---@see Config
---@param preset "noauto"?
---@return Config
local function getCfg(preset)
---@type Config
local base = {
fontFamily = defaultFontFamily,
textColor = nil,
backgroundColor = nil,
masking = nil,
autoScroll = true,
autoNewLine = true,
autoWrapMode = "b",
autoWrapLen = nil,
avoidBorder = true,
tabLen = 2
}
if preset == "noauto" then
base.autoScroll = false
base.autoNewLine = false
base.autoWrapMode = "n"
end
return base
end
---`str` should be read in `"rb"` mode from file <br>
---`cfg` see [`getCfg()`](lua://Config)
---### Config
---- **textColor**
---- **backgroundColor**
---- **fontFamily** use [`getFontFamily()`](lua://FontFamily) to modify
---- **masking** representing x1, y1, x2, y2 of 2 coordinates, concent out of the rectange range won't print
---- **autoScroll** if true, if next line is over-height, [`term.scroll()`](https://tweaked.cc/module/term.html#v:scroll) will be called, masking will also be scrolled
---- **autoNewLine** if true, the output will be like a `\n` added to the end
---- **autoWrapMode** <br>
----- `"n"` do not auto wrap<br>
----- `"b"` English letter will not be broken<br>
----- `"-"` English letter will be broken by a `'-'`<br>
---here 'letter' matches regex `(?<![a-zA-Z'])[a-zA-Z']+-?`<br>
---actually realized avoid using `luautf8` or regex matching
---- **autoWrapLen** the maximum distance a line goes from the terminal's left
---- **avoidBorder** if true, autoScroll and autoWrap will avoid printing border pixels, which have render issue<br>
---for example, autoWrap will start new line at pos (2, y) instead of (1, y)
---- **tabLen** the count of `' '` to replace `'\t'`
---@param str string
---@param cfg Config?
local function printUtf8(str, cfg)
local cursorX, cursorY = term.getCursorPos()
local termWidth, termHeight = term.getSize()
local cfg = cfg or getCfg()
local oriTextColor, oriBackgroundColor = term.getTextColor(), term.getBackgroundColor()
local textColor = cfg.textColor or oriTextColor
local backgroundColor = cfg.backgroundColor or oriBackgroundColor
local masking = cfg.masking
local autoScroll = cfg.autoScroll or true
local autoNewLine = cfg.autoNewLine
local autoWrapMode = cfg.autoWrapMode or "b"
local autoWrapLen = cfg.autoWrapLen or termWidth
local avoidBorder = cfg.avoidBorder
local fontFamily = cfg.fontFamily or defaultFontFamily
local tabLen = cfg.tabLen or 2
str = string.gsub(str, '\t', string.rep(" ", tabLen))
local fontHeight = fontFamily.maxHeight
if avoidBorder and autoWrapMode ~= "n" then
autoWrapLen = math.min(termWidth - 1, autoWrapLen)
cursorX = math.max(2, cursorX)
cursorY = math.max(2, cursorY)
end
local maxHeight = avoidBorder and termHeight - 1 or termHeight
---@type integer[]
local letterBuffer = {}
local dashWidth = #getCharMap(45, fontFamily)[1]
---@type { pos: [integer, integer], code: integer }[]
local charBuffer = {}
---@param x integer
---@param y integer
---@return boolean
local function bInMasking(x, y)
---@diagnostic disable-next-line
return x >= masking[1] and x <= masking[3] and y >= masking[2] and y <= masking[4]
end
---add new line to charBuffer
local function posNewLine()
cursorX = avoidBorder and 2 or 1
if autoScroll and cursorY + fontHeight > maxHeight then
-- scroll term and masking
term.scroll(fontHeight)
for _, char in ipairs(charBuffer) do
char.pos[2] = char.pos[2] - fontHeight
end
if masking then
masking[2] = masking[2] - fontHeight
masking[4] = masking[4] - fontHeight
end
else
cursorY = cursorY + fontHeight
end
end
---print char in charBuffer
local function printChar()
local bIsLastReversed = false
term.setTextColor(textColor)
term.setBackgroundColor(backgroundColor)
for _, char in ipairs(charBuffer) do
---@type bitmap
local charMap
local code = char.code
for _, font in ipairs(fontFamily) do
if font[code] then
charMap = font[code]
break
end
end
local width, height = #charMap[1], #charMap
local cursorX, cursorY = unpack(char.pos)
if height < fontHeight then
if bIsLastReversed then
bIsLastReversed = false
term.setTextColor(textColor)
term.setBackgroundColor(backgroundColor)
end
for _ = 1, fontHeight - height do
term.setCursorPos(cursorX, cursorY)
term.write(string.rep(" ", width))
cursorY = cursorY + 1
end
end
for y = 1, height do
local posY = cursorY + y - 1
term.setCursorPos(cursorX, posY)
local sCharMapBuffer = charMap[y]
for x = 1, width do
if not masking or bInMasking(cursorX + x - 1, posY) then
local code = string.byte(sCharMapBuffer, x)
if code < 128 then
code = code + 128
if not bIsLastReversed then
bIsLastReversed = true
term.setTextColor(backgroundColor)
term.setBackgroundColor(textColor)
end
else
if bIsLastReversed then
bIsLastReversed = false
term.setTextColor(textColor)
term.setBackgroundColor(backgroundColor)
end
end
term.write(string.char(code))
else
term.setCursorPos(cursorX + x, posY)
end
end
end
end
term.setTextColor(oriTextColor)
term.setBackgroundColor(oriBackgroundColor)
if autoNewLine then
term.setCursorPos(1, cursorY + fontHeight)
else
term.setCursorPos(cursorX, cursorY)
end
end
local bIsLetter = false
local function releaseLetter()
if bIsLetter then
if autoWrapMode == "-" then
local widthSum = cursorX - 1
local widthBuffer = {}
for index, letter in ipairs(letterBuffer) do
widthBuffer[index] = #getCharMap(letter, fontFamily)[1]
end
local ind = 1
local letterBufferLen = #letterBuffer
local lastDashPos = 1
while ind <= letterBufferLen do
widthSum = widthSum + widthBuffer[ind]
if widthSum > autoWrapLen then
while widthSum + dashWidth > autoWrapLen do
if ind == lastDashPos then
-- when have to insert dash in the first character, posNewLine() or throw
if ind == 1 and cursorX ~= (avoidBorder and 2 or 1) then
widthSum = 0
else
error("dash too wide or autoWrapLen too small")
end
end
widthSum = widthSum - widthBuffer[ind]
ind = ind - 1
end
-- pos letters before dash
for i = lastDashPos, ind do
charBuffer[#charBuffer + 1] = {
pos = { cursorX, cursorY },
code = letterBuffer[i]
}
cursorX = cursorX + widthBuffer[i]
end
if widthSum >= 0 then
-- add dash
charBuffer[#charBuffer + 1] = {
pos = { cursorX, cursorY },
code = 45
}
end
-- reset
widthSum = 0
lastDashPos = ind + 1
posNewLine()
end
ind = ind + 1
end
-- pos letters after dash
for i = lastDashPos, letterBufferLen do
charBuffer[#charBuffer + 1] = {
pos = { cursorX, cursorY },
code = letterBuffer[i]
}
cursorX = cursorX + widthBuffer[i]
end
elseif autoWrapMode == "b" then
local widthSum = cursorX - 1
local widthBuffer = {}
local ind = 1
local letterBufferLen = #letterBuffer
while ind <= letterBufferLen do
local charMapWidth = #getCharMap(letterBuffer[ind], fontFamily)[1]
widthBuffer[ind] = charMapWidth
widthSum = widthSum + charMapWidth
ind = ind + 1
end
local forceNewLineFlag = false
if widthSum > autoWrapLen then
if cursorX == (avoidBorder and 2 or 1) then
forceNewLineFlag = true
else
widthSum = widthSum - cursorX + 1
if widthSum > autoWrapLen then
forceNewLineFlag = true
else
posNewLine()
end
end
end
if forceNewLineFlag then
for i = 1, letterBufferLen do
if cursorX + widthBuffer[i] > autoWrapLen then
posNewLine()
end
charBuffer[#charBuffer + 1] = {
pos = { cursorX, cursorY },
code = letterBuffer[i]
}
cursorX = cursorX + widthBuffer[i]
end
else
for i = 1, letterBufferLen do
charBuffer[#charBuffer + 1] = {
pos = { cursorX, cursorY },
code = letterBuffer[i]
}
cursorX = cursorX + widthBuffer[i]
end
end
elseif autoWrapMode == "n" then
for _, letter in ipairs(letterBuffer) do
charBuffer[#charBuffer + 1] = {
pos = { cursorX, cursorY },
code = letter
}
cursorX = cursorX + #getCharMap(letter, fontFamily)[1]
end
end
letterBuffer = {}
bIsLetter = false
end
end
local lastLR = false
for _, code in codes(str) do
-- handle break line, \r, \n and \r\n will be transferred
if code == 13 then
releaseLetter()
lastLR = true
posNewLine()
elseif code == 10 and not lastLR then
lastLR = false
releaseLetter()
posNewLine()
-- record letter for break line
elseif code == 45 and bIsLetter then
lastLR = false
letterBuffer[#letterBuffer + 1] = code
releaseLetter()
elseif code >= 65 and code <= 90 or (code >= 97 and code <= 122) or code == 39 then
lastLR = false
letterBuffer[#letterBuffer + 1] = code
bIsLetter = true
-- pos char
else
lastLR = false
releaseLetter()
local charMapWidth = #getCharMap(code, fontFamily)[1]
if autoWrapMode ~= "n" and cursorX + charMapWidth - 1 > autoWrapLen then
posNewLine()
end
charBuffer[#charBuffer + 1] = {
pos = { cursorX, cursorY },
code = code
}
cursorX = cursorX + charMapWidth
end
end
releaseLetter()
printChar()
end
return {
codes = codes,
printUtf8 = printUtf8,
getFontFamily = getFontFamily,
sub = sub,
getCfg = getCfg,
getCharMap = getCharMap
}