I would also go for a procedural mesh approach. I once made a computercraft clone in Unity (though it was never really finished ^^). I have MoonSharp up and running (single thread, so would even work for WebGL builds and full lua coroutine support and infinite loop handling).
I made a text renderer screen that consists of many quads, one for each character. I used an old DOS font texture. I made a simple shader that uses the vertex color as foreground color and a second uv channel as background color. The alpha channel of the background color is even used for an automatic blinking behaviour. This is used for the cursor quad which is just an additional quad at the end of the vertices list. When I want to write a character to the screen, all I have to do is set the 4 uv coordinates of the 4 corners of the corresponding quad to the character I’m interested in. This update is extremely fast. So in the end I have a single mesh with a single material that can render the whole screen in one drawcall and it looks like this.

This is actually a lua program controlling the screen and it actually uses the same event system as computercraft. So in the end I want usual CC programs to be compatible. So maybe, one day, it will become a full turtle simulator
Though I don’t have much time recently so it’s currently just another project on hold 
lua code
If someones interested in it, this is the actual lua code running on the lua runtime.
local function test()
error("111",222)
coroutine.yield("test", 2)
end
print("test started")
term.clear()
term.setCursorPos(1,1)
term.setBackgroundColor(8)
term.write("test Text 132")
term.setBackgroundColor(128*256)
local co = coroutine.create(test)
local res = {coroutine.resume(co)}
print("result: " ,unpack(res))
local text = "Hello World"
term.blit(text, string.rep("f",#text), string.rep("0",#text))
while true do
local e,k = coroutine.yield()
if e == "char" then
if k=="\r" or k=="\n" then
local x,y = term.getCursorPos()
term.setCursorPos(1,y+1)
else
term.write(k)
print("event: "..tostring(e).." = " .. tostring(k))
end
elseif e == "key" then
if k == 293 then --F12
break;
elseif k == 8 then
local x,y = term.getCursorPos()
term.setCursorPos(x-1,y)
term.write(" ")
term.setCursorPos(x-1,y)
elseif k == 273 then --up
local x,y = term.getCursorPos()
term.setCursorPos(x,y-1)
elseif k == 274 then --down
local x,y = term.getCursorPos()
term.setCursorPos(x,y+1)
elseif k == 275 then --right
local x,y = term.getCursorPos()
term.setCursorPos(x+1,y)
elseif k == 276 then --left
local x,y = term.getCursorPos()
term.setCursorPos(x-1,y)
elseif k == 127 then --del
local x,y = term.getCursorPos()
term.write(" ")
term.setCursorPos(x,y)
else
--term.write(" " .. k)
--273 Up
--274 Down
--275 Right
--276 Left
--280 PGUp
--281 PGDn
--127 Del
--278 Home
--279 End
--277 Ins
end
end
end
Here are a few parts of the TextScreenRenderer:
public void SetCharUV(int aIndex, char aChar)
{
aIndex *= 4;
int i = (int)aChar;
if (i < m_RevKeyMap.Length)
i = m_RevKeyMap[i];
var v = new Vector2(i % 16, -i / 16)/16;
m_UVs[aIndex + 0] = v;
m_UVs[aIndex + 1] = new Vector2(v.x + 1f / 16, v.y);
m_UVs[aIndex + 2] = new Vector2(v.x + 1f / 16, v.y - 1f / 16);
m_UVs[aIndex + 3] = new Vector2(v.x, v.y - 1f / 16);
}
public void SetCharColor(int aIndex, Color32 aColor)
{
aIndex *= 4;
m_Colors[aIndex] = m_Colors[aIndex + 1] = m_Colors[aIndex + 2] = m_Colors[aIndex + 3] = aColor;
}
public void SetCharBkColor(int aIndex, Color aColor)
{
aIndex *= 4;
m_BkColors[aIndex] = m_BkColors[aIndex + 1] = m_BkColors[aIndex + 2] = m_BkColors[aIndex + 3] = aColor;
}
public void Write(string aText)
{
for(int i = 0; i < aText.Length; i++)
{
if (!IsClipped)
{
int index = m_Cursor.x + m_Cursor.y * m_ScreenSize.x;
SetCharUV(index, aText[i]);
SetCharColor(index, m_TextColor);
SetCharBkColor(index, m_BkColor);
}
m_Cursor.x++;
}
SetCursorChar(m_Cursor, m_CursorState);
}
public void WriteWrapped(string aText)
{
for (int i = 0; i < aText.Length; i++)
{
if (!IsClipped)
{
int index = m_Cursor.x + m_Cursor.y * m_ScreenSize.x;
SetCharUV(index, aText[i]);
SetCharColor(index, m_TextColor);
SetCharBkColor(index, m_BkColor);
}
m_Cursor.x++;
if (m_Cursor.x >= m_ScreenSize.x)
{
m_Cursor.x = 0;
m_Cursor.y++;
}
}
SetCursorChar(m_Cursor, m_CursorState);
}
public void UpdateMesh()
{
m_Mesh.SetUVs(0, m_UVs);
m_Mesh.SetColors(m_Colors);
m_Mesh.SetUVs(1, m_BkColors);
}