Unicode Viewer: Experiments with Unicode

The experiment below (updated for Codea version 1.5.1), for exploring pages of Unicode characters, was created around the same time that I was experimenting with emoji. (Update: now also allows for an emoji-friendly font to be chosen, when required.)

--
-- Unicode Viewer
--
supportedOrientations(LANDSCAPE_ANY)
function setup()
    PageLow, PageHigh, Plane = 0, 0, 0
    page = (Plane * 0x100 + PageHigh * 0x10 + PageLow) * 0x100
    img = image(WIDTH, HEIGHT)
    parameter.integer("PageLow", 0, 15, PageLow, changed)
    parameter.integer("PageHigh", 0, 15, PageHigh, changed)
    parameter.integer("Plane", 0, 2, Plane, changed)
    parameter.boolean("isEmojiFriendly", true, efChanged)
    fill(255)
    stroke(0, 255, 0)
    strokeWidth(2.5)
    spriteMode(CENTER)
    textMode(CENTER)
    rectMode(CENTER)
    isZoomed = false
    pickedCharUnicode = nil
    pickedChar = nil
    print("Tap to zoom in")
    print("Tap to zoom out.")
    print()
    print("A white box means no character is defined for that code.")
end

function changed()
    page = (Plane * 0x100 + PageHigh * 0x10 + PageLow) * 0x100
    latency = ElapsedTime + 0.08
    redraw = true
    isZoomed = false
end

function efChanged()
    if isEmojiFriendly then
        f = "AppleColorEmoji"
    else
        f = "ArialMT"
    end
    drawPage(img, page)
end

function drawPage(img, base)
    setContext(img)
    background(0)
    fill(255)
    font(f)
    fontSize(40)
    for j = 0, 15 do
        for i = 0, 15 do
            local u = base + i * 0x10 + j
            local char = unicode2UTF8(u)
            local x = WIDTH/16 * (i + 1/2)
            local y = HEIGHT/16 * (15 - j + 1/2)
            text(char, x, y)
        end
    end
    setContext()
end

function draw()
    background(0)
    if redraw and ElapsedTime > latency then
        drawPage(img, page)
        redraw = false
    end
    if isZoomed then
        line(0, HEIGHT/2, WIDTH, HEIGHT/2)
        line(WIDTH/2, 0, WIDTH/2, HEIGHT)
        font(f)
        fontSize(512)
        local w, h = textSize(pickedChar)
        local fm = fontMetrics()
        noFill()
        rect(WIDTH/2, HEIGHT/2, w, h)
        fill(255)
        text(pickedChar, WIDTH/2, HEIGHT/2)
        fontSize(64)
        fill(0)
        text(pickedChar, WIDTH/2 + 1, 149)
        fill(255)
        text(pickedChar, WIDTH/2, 150)
        font("Inconsolata")
        fontSize(64)
        local title = string.format("U+%4X", pickedCharUnicode)..
            " (= "..pickedCharUnicode..")"
        text(title, WIDTH/2, 50)
    else
        sprite(img, WIDTH/2, HEIGHT/2)
    end 
end

function touched(touch)
    if touch.state == BEGAN then
        if isZoomed then
            isZoomed = false
        else
            isZoomed = true
            local i = math.floor(touch.x/WIDTH * 16)
            local j = 15 - math.floor(touch.y/HEIGHT * 16)
            pickedCharUnicode = page + i * 0x10 + j
            pickedChar = unicode2UTF8(pickedCharUnicode)
        end
    end
end            

-- Unicode code point to UTF-8 format string of bytes
--
-- Bit Last point Byte 1   Byte 2   Byte 3   Byte 4   Byte 5   Byte 6
-- 7   U+007F     0xxxxxxx
-- 11  U+07FF     110xxxxx 10xxxxxx
-- 16  U+FFFF     1110xxxx 10xxxxxx 10xxxxxx
-- 21  U+1FFFFF   11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
-- 26  U+3FFFFFF  111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
-- 31  U+7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
--
-- However, largest integer in Codea's Lua is 0x7FFFFF (23 bit)
-- With acknowledgement also to Andrew Stacey's UTF-8 library
function unicode2UTF8(u)
    u = math.max(0, math.floor(u)) -- A positive integer
    local UTF8
    if u < 0x80 then          -- less than  8 bits
        UTF8 = string.char(u)
    elseif u < 0x800 then     -- less than 12 bits
        local b2 = u % 0x40 + 0x80
        local b1 = math.floor(u/0x40) + 0xC0
        UTF8 = string.char(b1, b2)
    elseif u < 0x10000 then   -- less than 16 bits
        local b3 = u % 0x40 + 0x80
        local b2 = math.floor(u/0x40) % 0x40 + 0x80
        local b1 = math.floor(u/0x1000) + 0xE0
        UTF8 = string.char(b1, b2, b3)
    elseif u < 0x200000 then  -- less than 22 bits
        local b4 = u % 0x40 + 0x80
        local b3 = math.floor(u/0x40) % 0x40 + 0x80
        local b2 = math.floor(u/0x1000) % 0x40 + 0x80
        local b1 = math.floor(u/0x40000) + 0xF0
        UTF8 = string.char(b1, b2, b3, b4)
    elseif u < 0x800000 then -- less than 24 bits
        local b5 = u % 0x40 + 0x80
        local b4 = math.floor(u/0x40) % 0x40 + 0x80
        local b3 = math.floor(u/0x1000) % 0x40 + 0x80
        local b2 = math.floor(u/0x40000) % 0x40 + 0x80
        local b1 = math.floor(u/0x1000000) + 0xF8
        UTF8 = string.char(b1, b2, b3, b4, b5)
    else
        print("Error: Code point too large for Codea's Lua.")
    end
    return UTF8
end

```


Drawing a page of 256 characters with `text()` takes a little time. To reduce the effects of latency on the parameter sliders, and when zooming in or out, I use an image (`img`) to hold the current page of characters and introduce a 0.08 second delay between the last change of the parameter sliders and the redrawing of the page (by `drawPage()`).