Standardise on term colour parsing

- colors.toBlit now performs bounds checks on the passed value,
   preventing weird behaviour like color.toBlit(2 ^ 16) returning "10".

 - The window API now uses colors.toBlit (or rather a copy of it) for
   parsing colours, allowing doing silly things like
   term.setTextColour(colours.blue + 5).

 - Add some top-level documentation to the term API to explain some of
   the basics.

Closes #1736
This commit is contained in:
Jonathan Coates 2024-03-06 10:18:09 +00:00
parent 4daa2a2b6a
commit 6e374579a4
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
5 changed files with 86 additions and 25 deletions

View File

@ -13,8 +13,54 @@
/**
* Interact with a computer's terminal or monitors, writing text and drawing
* ASCII graphics.
* Interact with a computer's terminal or monitors, writing text and drawing ASCII graphics.
*
* <h2>Writing to the terminal</h2>
* The simplest operation one can perform on a terminal is displaying (or writing) some text. This can be performed with
* the [`term.write`] method.
*
* <pre>{@code
* term.write("Hello, world!")
* }</pre>
* <p>
* When you write text, this advances the cursor, so the next call to [`term.write`] will write text immediately after
* the previous one.
*
* <pre>{@code
* term.write("Hello, world!")
* term.write("Some more text")
* }</pre>
* <p>
* [`term.getCursorPos`] and [`term.setCursorPos`] can be used to manually change the cursor's position.
* <p>
* <pre>{@code
* term.clear()
*
* term.setCursorPos(1, 1) -- The first column of line 1
* term.write("First line")
*
* term.setCursorPos(20, 2) -- The 20th column of line 2
* term.write("Second line")
* }</pre>
* <p>
* [`term.write`] is a relatively basic and low-level function, and does not handle more advanced features such as line
* breaks or word wrapping. If you just want to display text to the screen, you probably want to use [`print`] or
* [`write`] instead.
*
* <h2>Colours</h2>
* So far we've been writing text in black and white. However, advanced computers are also capable of displaying text
* in a variety of colours, with the [`term.setTextColour`] and [`term.setBackgroundColour`] functions.
*
* <pre>{@code
* print("This text is white")
* term.setTextColour(colours.green)
* print("This text is green")
* }</pre>
* <p>
* These functions accept any of the constants from the [`colors`] API. [Combinations of colours][`colors.combine`] may
* be accepted, but will only display a single colour (typically following the behaviour of [`colors.toBlit`]).
* <p>
* The [`paintutils`] API provides several helpful functions for displaying graphics using [`term.setBackgroundColour`].
*
* @cc.module term
*/

View File

@ -353,7 +353,8 @@ end
--[[- Converts the given color to a paint/blit hex character (0-9a-f).
This is equivalent to converting floor(log_2(color)) to hexadecimal.
This is equivalent to converting `floor(log_2(color))` to hexadecimal. Values
outside the range of a valid colour will error.
@tparam number color The color to convert.
@treturn string The blit hex code of the color.
@ -367,7 +368,11 @@ colors.toBlit(colors.red)
]]
function toBlit(color)
expect(1, color, "number")
return color_hex_lookup[color] or string.format("%x", math.floor(math.log(color, 2)))
local hex = color_hex_lookup[color]
if hex then return hex end
if color < 0 or color > 0xffff then error("Colour out of range", 2) end
return string.format("%x", math.floor(math.log(color, 2)))
end
--[[- Converts the given paint/blit hex character (0-9a-f) to a color.

View File

@ -58,6 +58,17 @@ local type = type
local string_rep = string.rep
local string_sub = string.sub
--- A custom version of [`colors.toBlit`], specialised for the window API.
local function parse_color(color)
if type(color) ~= "number" then
-- By tail-calling expect, we ensure expect has the right error level.
return expect(1, color, "number")
end
if color < 0 or color > 0xffff then error("Colour out of range", 3) end
return 2 ^ math.floor(math.log(color, 2))
end
--[[- Returns a terminal object that is a space within the specified parent
terminal object. This can then be used (or even redirected to) in the same
manner as eg a wrapped monitor. Refer to [the term API][`term`] for a list of
@ -341,10 +352,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
end
local function setTextColor(color)
if type(color) ~= "number" then expect(1, color, "number") end
if tHex[color] == nil then
error("Invalid color (got " .. color .. ")" , 2)
end
if tHex[color] == nil then color = parse_color(color) end
nTextColor = color
if bVisible then
@ -356,11 +364,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
window.setTextColour = setTextColor
function window.setPaletteColour(colour, r, g, b)
if type(colour) ~= "number" then expect(1, colour, "number") end
if tHex[colour] == nil then
error("Invalid color (got " .. colour .. ")" , 2)
end
if tHex[colour] == nil then colour = parse_color(colour) end
local tCol
if type(r) == "number" and g == nil and b == nil then
@ -385,10 +389,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
window.setPaletteColor = window.setPaletteColour
function window.getPaletteColour(colour)
if type(colour) ~= "number" then expect(1, colour, "number") end
if tHex[colour] == nil then
error("Invalid color (got " .. colour .. ")" , 2)
end
if tHex[colour] == nil then colour = parse_color(colour) end
local tCol = tPalette[colour]
return tCol[1], tCol[2], tCol[3]
end
@ -396,10 +397,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
window.getPaletteColor = window.getPaletteColour
local function setBackgroundColor(color)
if type(color) ~= "number" then expect(1, color, "number") end
if tHex[color] == nil then
error("Invalid color (got " .. color .. ")", 2)
end
if tHex[color] == nil then color = parse_color(color) end
nBackgroundColor = color
end

View File

@ -92,6 +92,11 @@ describe("The colors library", function()
it("floors colors", function()
expect(colors.toBlit(16385)):eq("e")
end)
it("errors on out-of-range colours", function()
expect.error(colors.toBlit, -120):eq("Colour out of range")
expect.error(colors.toBlit, 0x10000):eq("Colour out of range")
end)
end)
describe("colors.fromBlit", function()

View File

@ -58,7 +58,14 @@ describe("The window library", function()
w.setTextColour(colors.white)
expect.error(w.setTextColour, nil):eq("bad argument #1 (number expected, got nil)")
expect.error(w.setTextColour, -5):eq("Invalid color (got -5)")
expect.error(w.setTextColour, -5):eq("Colour out of range")
end)
it("supports invalid combined colours", function()
local w = mk()
w.setTextColour(colours.combine(colours.red, colours.green))
expect(w.getTextColour()):eq(colours.red)
end)
end)
@ -69,7 +76,7 @@ describe("The window library", function()
w.setPaletteColour(colors.white, 0x000000)
expect.error(w.setPaletteColour, nil):eq("bad argument #1 (number expected, got nil)")
expect.error(w.setPaletteColour, -5):eq("Invalid color (got -5)")
expect.error(w.setPaletteColour, -5):eq("Colour out of range")
expect.error(w.setPaletteColour, colors.white):eq("bad argument #2 (number expected, got nil)")
expect.error(w.setPaletteColour, colors.white, 1, false):eq("bad argument #3 (number expected, got boolean)")
expect.error(w.setPaletteColour, colors.white, 1, nil, 1):eq("bad argument #3 (number expected, got nil)")
@ -82,7 +89,7 @@ describe("The window library", function()
local w = mk()
w.getPaletteColour(colors.white)
expect.error(w.getPaletteColour, nil):eq("bad argument #1 (number expected, got nil)")
expect.error(w.getPaletteColour, -5):eq("Invalid color (got -5)")
expect.error(w.getPaletteColour, -5):eq("Colour out of range")
end)
end)
@ -92,7 +99,7 @@ describe("The window library", function()
w.setBackgroundColour(colors.white)
expect.error(w.setBackgroundColour, nil):eq("bad argument #1 (number expected, got nil)")
expect.error(w.setBackgroundColour, -5):eq("Invalid color (got -5)")
expect.error(w.setBackgroundColour, -5):eq("Colour out of range")
end)
end)