Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- CONSTANTS --
- local PAGE_WIDTH = 25
- local PAGE_HEIGHT = 21
- local MIN_BLOCK_SIZE_SPLIT = 5
- local PAGE_COLOR = colors.white
- local TEXT_COLOR = colors.black
- local BG_COLOR = colors.lightGray
- local SHADOW_COLOR = colors.gray
- local DISPLAY_PAGE_HEIGHT = PAGE_HEIGHT+4
- -- FUNCTION DECLARATIONS --
- -- this shouldn't be necessary, but things weren't working otherwise
- local splitBlocks
- local classifyElements
- local renderElement
- -- PATTERNS --
- local emptyPattern = "^%s*$"
- local indentPattern = "^%s%s+(.+)"
- local headerPattern = "^#+%s*(.-)%s*#*$"
- local pageBreakPattern = "^//+$"
- local unorderedListPattern = "^%s*[%*%+%-]%s*(.+)"
- local orderedListPattern = "^[%s]*%d+%.%s*(.+)"
- local blockquotePattern = "^>%s*(.*)" -- there may be a line in a blockquote that is otherwise empty
- -- PATTERN FUNCTIONS --
- local function processBlock(block)
- local text = table.concat(block, " ")
- return {type = "block", content = string.gsub(text, "%s+", " ")}
- end
- local function processHeader(block)
- local _, _, content = string.find(block[1], headerPattern)
- return {type = "header", content = content}
- end
- local function processPageBreak(block)
- return {type = "pagebreak"}
- end
- local function processUnorderedList(block)
- local items = {}
- local current = {}
- for i = 1, #block do
- local _, _, text = string.find(block[i], unorderedListPattern)
- if text and #current > 0 then
- items[#items+1] = processBlock(current)
- else
- current[#current+1] = text or block[i]
- end
- end
- if #current > 0 then
- items[#items+1] = processBlock(current)
- end
- return {type = "unorderedlist", content = items}
- end
- local function processOrderedList(block)
- local items = {}
- local current = {}
- for i = 1, #block do
- local _, _, text = string.find(block[i], orderedListPattern)
- if text and #current > 0 then
- items[#items+1] = processBlock(current)
- current = {text}
- else
- current[#current+1] = text or block[i]
- end
- end
- if #current > 0 then
- items[#items+1] = processBlock(current)
- end
- return {type = "orderedlist", content = items}
- end
- local function processBlockquote(block)
- local rawContent = {}
- for i = 1, #block do
- local _, _, text = string.find(block[i], blockquotePattern)
- rawContent[#rawContent+1] = text or block[i]
- end
- local childBlocks = splitBlocks(rawContent)
- local childElements = classifyElements(childBlocks)
- return {type = "blockquote", content = childElements}
- end
- -- PROCESSING CODE --
- function splitBlocks(lines)
- local blocks = {}
- local current = {}
- for i = 1, #lines do
- if string.find(lines[i], emptyPattern) then
- if #current > 0 then
- blocks[#blocks+1] = current
- current = {}
- end
- else
- current[#current+1] = lines[i]
- end
- end
- -- catch stragglers
- if #current > 0 then
- blocks[#blocks+1] = current
- end
- return blocks
- end
- function classifyElements(blocks)
- local patternFunctions = {}
- patternFunctions[headerPattern] = processHeader
- patternFunctions[pageBreakPattern] = processPageBreak
- patternFunctions[unorderedListPattern] = processUnorderedList
- patternFunctions[orderedListPattern] = processOrderedList
- patternFunctions[blockquotePattern] = processBlockquote
- local elements = {}
- for i = 1, #blocks do
- local current = blocks[i]
- local found = false
- for p, f in pairs(patternFunctions) do
- if string.find(current[1], p) then
- found = true
- elements[#elements+1] = f(current)
- break
- end
- end
- if not found then
- elements[#elements+1] = processBlock(current)
- end
- end
- return elements
- end
- local function groupLists(elements)
- local newElements = {}
- local i = 1
- while i <= #elements do
- if elements[i].type == "blockquote" then
- elements[i].content = groupLists(elements[i].content)
- elseif elements[i].type == "orderedlist" or
- elements[i].type == "unorderedlist" then
- while i < #elements and elements[i+1].type == elements[i].type do
- for k = 1, #elements[i+1].content do
- elements[i].content[#elements[i].content+1] = elements[i+1].content[k]
- end
- table.remove(elements, i+1)
- end
- end
- newElements[#newElements+1] = elements[i]
- i = i+1
- end
- return newElements
- end
- -- TEXT MANIPULATION --
- local function loadFileLines(file)
- local lines = {}
- local handle = fs.open(file, "r")
- while true do
- local line = handle.readLine()
- if line ~= nil then
- lines[#lines+1] = line
- else
- break
- end
- end
- handle.close()
- return lines
- end
- -- RENDERING AND PAGINATION --
- local function renderBlock(block, maxWidth, justify)
- justify = justify or "left"
- local lines = {""}
- for word, delim in string.gmatch(block.content, "([^%s-]+)([%s-]?)") do
- if #lines[#lines] == 0 then
- lines[#lines] = word..delim
- else
- if delim == "-" then
- if #lines[#lines]+#word+#delim <= maxWidth then
- lines[#lines] = lines[#lines]..word..delim
- else
- lines[#lines+1] = word..delim
- end
- else
- if #lines[#lines]+#word <= maxWidth then
- lines[#lines] = lines[#lines]..word
- if #lines[#lines]+#delim then
- lines[#lines] = lines[#lines]..delim
- else
- lines[#lines+1] = ""
- end
- else
- lines[#lines+1] = word..delim
- end
- end
- end
- end
- for i = 1, #lines do
- if justify == "center" then
- local l = #lines[i]
- lines[i] = string.rep(" ", math.floor((maxWidth-l)/2))..lines[i]
- elseif justify == "right" then
- local l = #lines[i]
- lines[i] = string.rep(" ", maxWidth-l)..lines[i]
- end
- end
- return lines
- end
- local function renderHeader(block, width)
- return renderBlock(block, width, "center")
- end
- local function renderOrderedList(block, width)
- local count = #block.content
- local indent = 4+math.floor(math.log10(count))
- local lines = {}
- for i = 1, #block.content do
- local current = block.content[i]
- local prefix = i..". "
- prefix = string.rep(" ", indent-#prefix)..prefix
- local blockLines = renderBlock(current, width-indent)
- lines[#lines+1] = prefix..blockLines[1]
- for j = 2, #blockLines do
- lines[#lines+1] = string.rep(" ", indent)..blockLines[j]
- end
- if i < #block.content then
- lines[#lines+1] = ""
- end
- end
- return lines
- end
- local function renderUnorderedList(block, width)
- local prefix = " \132 "
- local lines = {}
- for i = 1, #block.content do
- local current = block.content[i]
- local blockLines = renderBlock(current, width-#prefix)
- lines[#lines+1] = prefix..blockLines[1]
- for j = 2, #blockLines do
- lines[#lines+1] = string.rep(" ", #prefix)..blockLines[j]
- end
- if i < #block.content then
- lines[#lines+1] = ""
- end
- end
- return lines
- end
- local function renderBlockquote(block, width)
- local prefix = '\149' -- pretty-printing
- local lines = {}
- for i = 1, #block.content do
- if block.content[i].type ~= "pagebreak" then -- it really doesn't make sense in a blockquote
- local blockLines = renderElement(block.content[i], width-#prefix)
- for j = 1, #blockLines do
- lines[#lines+1] = prefix..blockLines[j]
- end
- if i < #block.content then
- lines[#lines+1] = prefix
- end
- end
- end
- return lines
- end
- function renderElement(element, width)
- local renderingFunctions = {}
- renderingFunctions.header = renderHeader
- renderingFunctions.block = renderBlock
- renderingFunctions.unorderedlist = renderUnorderedList
- renderingFunctions.orderedlist = renderOrderedList
- renderingFunctions.blockquote = renderBlockquote
- if renderingFunctions[element.type] then
- return renderingFunctions[element.type](element, width)
- end
- return nil
- end
- local function paginate(elements)
- local pages = {{}}
- for i = 1, #elements do
- if elements[i].type == "pagebreak" then
- if i < #elements and #pages[#pages] > 0 then
- pages[#pages+1] = {}
- end
- else
- local block = renderElement(elements[i], PAGE_WIDTH)
- local current = pages[#pages]
- if #current > 0 and current[#current] ~= "" then
- if #current >= PAGE_HEIGHT then
- current = {}
- pages[#pages+1] = current
- else
- current[#current+1] = ""
- end
- end
- if #current+#block > PAGE_HEIGHT and #block < MIN_BLOCK_SIZE_SPLIT then
- current = block
- pages[#pages+1] = current
- else
- for j = 1, #block do
- if #current >= PAGE_HEIGHT then
- current = {}
- pages[#pages+1] = current
- end
- current[#current+1] = block[j]
- end
- end
- end
- end
- return pages
- end
- -- PREVIEWING --
- local function drawPageLine(pages, width, height, pageLine, screenLine)
- local onPageLine = (pageLine-1)%(DISPLAY_PAGE_HEIGHT)+1
- term.setBackgroundColor(BG_COLOR)
- term.setCursorPos(1, screenLine)
- term.clearLine()
- if onPageLine == DISPLAY_PAGE_HEIGHT then
- local shadow = '\130'..string.rep('\131', PAGE_WIDTH+1)
- ..'\129'
- term.setBackgroundColor(BG_COLOR)
- term.setTextColor(SHADOW_COLOR)
- term.setCursorPos(math.floor((width-(#shadow-1))/2), screenLine)
- term.write(shadow)
- elseif onPageLine > 1 then
- local text = pages[math.ceil(pageLine/DISPLAY_PAGE_HEIGHT)][onPageLine-2]
- text = text or ""
- text = " "..text..string.rep(" ", PAGE_WIDTH-#text+1)
- term.setBackgroundColor(PAGE_COLOR)
- term.setTextColor(TEXT_COLOR)
- term.setCursorPos(math.floor((width-#text)/2), screenLine)
- term.write(text)
- term.setTextColor(SHADOW_COLOR)
- term.setBackgroundColor(BG_COLOR)
- if onPageLine == 2 then
- term.write('\148')
- else
- term.write('\149')
- end
- end
- end
- local function menu(choices)
- local currentItem = 1
- local scrW, scrH = term.getSize()
- term.setCursorPos(1, scrH)
- term.setBackgroundColor(colors.black)
- term.setTextColor(colors.white)
- term.clearLine()
- while true do
- term.setCursorPos(1, scrH)
- for i = 1, #choices do
- if i == currentItem then
- term.write("["..choices[i].."]")
- else
- term.write(" "..choices[i].." ")
- end
- end
- local _, key = os.pullEvent("key")
- if key == keys.left then
- currentItem = math.max(1, currentItem-1)
- elseif key == keys.right then
- currentItem = math.min(#choices, currentItem+1)
- elseif key == keys.enter then
- return choices[currentItem]
- elseif key == keys.leftCtrl or key == keys.rightCtrl then
- return nil
- end
- end
- end
- local function preview(pages)
- local scroll = 0
- local scrW, scrH = term.getSize()
- local doPrint = false
- local running = true
- for i = 1, scrH do
- drawPageLine(pages, scrW, scrH, i+scroll, i)
- end
- while running do
- event, a, b, c = os.pullEvent()
- if event == "term_resize" then
- scrW, scrH = term.getSize()
- for i = 1, scrH do
- drawPageLine(pages, scrW, scrH, i+scroll, i)
- end
- elseif event == "key" then
- local targetScroll = scroll
- if a == keys.down then
- targetScroll = math.min(scroll+1, #pages*DISPLAY_PAGE_HEIGHT-scrH)
- elseif a == keys.up then
- targetScroll = math.max(scroll-1, 0)
- elseif a == keys.pageDown then
- targetScroll = math.min(#pages*DISPLAY_PAGE_HEIGHT-scrH, scroll+scrH)
- elseif a == keys.pageUp then
- targetScroll = math.max(0, scroll-scrH)
- end
- local scrollDiff = targetScroll-scroll
- term.scroll(scrollDiff)
- scroll = targetScroll
- if scrollDiff > 0 then
- for i = scrH-scrollDiff+1, scrH do
- drawPageLine(pages, scrW, scrH, i+scroll, i)
- end
- elseif scrollDiff < 0 then
- for i = 1, -scrollDiff do
- drawPageLine(pages, scrW, scrH, i+scroll, i)
- end
- end
- if a == keys.leftCtrl or a == keys.rightCtrl then
- local action = menu({"Print", "Exit"})
- drawPageLine(pages, scrW, scrH, scrH+scroll, scrH)
- if action == "Print" then
- running = false
- doPrint = true
- end
- if action == "Exit" then
- running = false
- end
- end
- end
- end
- term.setBackgroundColor(colors.black)
- term.setTextColor(colors.white)
- term.clear()
- term.setCursorPos(1, 1)
- return doPrint
- end
- -- PRINTING --
- local function printOut(title, pages)
- local printer = peripheral.find("printer")
- if not printer then
- print("No printer detected.")
- return
- end
- for i = 1, #pages do
- if printer.getPaperLevel() == 0 then
- print("Please insert more paper.")
- while printer.getPaperLevel() == 0 do
- sleep(0.5)
- end
- end
- if printer.getInkLevel() == 0 then
- print("Please insert more ink.")
- while printer.getInkLevel() == 0 do
- sleep(0.5)
- end
- end
- printer.newPage()
- printer.setPageTitle(title..": page "..i)
- for j = 1, #pages[i] do
- printer.setCursorPos(1, j)
- printer.write(pages[i][j])
- end
- while not printer.endPage() do -- in case the tray is full
- sleep(0.5)
- end
- end
- end
- -- ENTRY CODE --
- local args = {...}
- if #args == 0 then
- print("Usage: "..shell.getRunningProgram().." <filename>")
- return
- end
- local filename = args[1]
- if not fs.exists(filename) then
- print(filename.." does not exist.")
- return
- end
- local lines = loadFileLines(filename)
- local blocks = splitBlocks(lines)
- local elements = classifyElements(blocks)
- elements = groupLists(elements)
- local pages = paginate(elements)
- local doPrint = preview(pages)
- if doPrint then
- printOut(filename, pages)
- end
Add Comment
Please, Sign In to add comment