Text banner
Big letters five rows high from a built-in font, wrapped to the screen; ordinary text wrapped greedily to a width and lined up left, centred or right; and a frame whose width comes from the longest line, so its edge closes exactly.
Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.
About
Big letters five rows high, wrapped to fit the screen. Ordinary text wrapped to a width and lined up left, centred or right. And a frame round it that fits the text exactly.
main.eml- the menu, reading text and widths, and printingbanner.eml- the 5 x 5 font, big letters, wrapping, lining up and framing
How each part works:
- Big letters: a font of 41 glyphs (A to Z, 0 to 9, space and ! ? . -), each five rows of five columns, with no two alike. Small letters are drawn as capitals. A banner line is the glyphs side by side with one space between, and no spaces left at the ends of the rows. 13 letters fit in 80 columns, so longer text is wrapped first.
- Wrapping is greedy: as many words as fit on each line, one space between them. A word longer than the width is cut into pieces that fit.
- Lining up: right puts the spaces in front; centre splits them, with an odd one going on the right. Nothing is added after the text.
- The frame, as in the corpus case
text-banner-builder, is as wide as the longest line plus the padding on both sides, worked out from the text rather than a fixed number. A wrong count would show as a ragged right edge.
What is checked: menu choices 1 to 4; text of up to 200 characters, and for big letters only the characters the font has; a width of 10 to 80 (10 to 70 inside a frame); padding of 0 to 4; left, centre or right. An empty answer cancels.
Sessions: sessions/basic.in gives: - "Hello EML 2026!" in big letters on two lines, and "eml-p rocks?" in capitals; - one sentence wrapped to 30 on the left, and to 20 centred; - a sentence with "internationalisation," cut to fit 15, on the right; - a sentence in a frame, 24 wide with padding 2, centred.
sessions/bad-input.in gives: - menu choices 0 and x; - "a,b", which the font cannot draw, and cancels; - widths 9, 81 and x, the alignment "middle", padding 5, then "abc" in the smallest frame; - a text over 200 characters.
Built on the verified corpus case text-banner-builder (sequence repetition, and a frame drawn to a width taken from the longest line).
Recorded sessions
What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.
bad-input
interpreter: byte-equal== Text banner ==
Big letters, wrapped and lined-up text, and a frame that fits.
1) big letters 2) wrap and line up 3) in a frame 4) quit
choice> 0
Pick a number from 1 to 4.
1) big letters 2) wrap and line up 3) in a frame 4) quit
choice> x
Pick a number from 1 to 4.
1) big letters 2) wrap and line up 3) in a frame 4) quit
choice> 1
text for the banner> a,b
The big letters are A to Z, 0 to 9, space and ! ? . -
text for the banner>
Cancelled.
1) big letters 2) wrap and line up 3) in a frame 4) quit
choice> 2
text>
Cancelled.
1) big letters 2) wrap and line up 3) in a frame 4) quit
choice> 2
text> abc
width (10 to 80)> 9
Type a whole number from 10 to 80.
width (10 to 80)> 81
Type a whole number from 10 to 80.
width (10 to 80)> x
Type a whole number from 10 to 80.
width (10 to 80)> 20
line up (left, centre or right)> middle
Type left, centre or right.
line up (left, centre or right)>
Cancelled.
1) big letters 2) wrap and line up 3) in a frame 4) quit
choice> 3
text> abc
width of the text (10 to 70)> 10
padding (0 to 4)> 5
Type a whole number from 0 to 4.
padding (0 to 4)> 0
line up (left, centre or right)> left
+---+
|abc|
+---+
1) big letters 2) wrap and line up 3) in a frame 4) quit
choice> 2
text> word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word
Up to 200 characters.
text>
Cancelled.
1) big letters 2) wrap and line up 3) in a frame 4) quit
choice> 4
Bye.
What was typed (25 lines)
0
x
1
a,b
2
2
abc
9
81
x
20
middle
3
abc
10
5
0
left
2
word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word
4
basic
interpreter: byte-equal== Text banner ==
Big letters, wrapped and lined-up text, and a frame that fits.
1) big letters 2) wrap and line up 3) in a frame 4) quit
choice> 1
text for the banner> Hello EML 2026!
# # ##### # # ### ##### # # #
# # # # # # # # ## ## #
##### #### # # # # #### # # # #
# # # # # # # # # # #
# # ##### ##### ##### ### ##### # # #####
### ### ### ### #
# # # ## # # # #
## # # # ## #### #
# ## # # # #
##### ### ##### ### #
1) big letters 2) wrap and line up 3) in a frame 4) quit
choice> 1
text for the banner> eml-p rocks?
##### # # # #### #### ### #### # # #### ###
# ## ## # # # # # # # # # # # # #
#### # # # # ##### #### #### # # # ### ### ##
# # # # # # # # # # # # #
##### # # ##### # # # ### #### # # #### #
1) big letters 2) wrap and line up 3) in a frame 4) quit
choice> 2
text> The quick brown fox jumps over the lazy dog, again and again.
width (10 to 80)> 30
line up (left, centre or right)> left
+----------------------------+
The quick brown fox jumps over
the lazy dog, again and again.
+----------------------------+
(the rule marks the width, 30 columns)
1) big letters 2) wrap and line up 3) in a frame 4) quit
choice> 2
text> The quick brown fox jumps over the lazy dog, again and again.
width (10 to 80)> 20
line up (left, centre or right)> centre
+------------------+
The quick brown fox
jumps over the lazy
dog, again and
again.
+------------------+
(the rule marks the width, 20 columns)
1) big letters 2) wrap and line up 3) in a frame 4) quit
choice> 2
text> Some words are longer than a line: internationalisation, for one.
width (10 to 80)> 15
line up (left, centre or right)> right
+-------------+
Some words are
longer than a
line:
internationalis
ation, for one.
+-------------+
(the rule marks the width, 15 columns)
1) big letters 2) wrap and line up 3) in a frame 4) quit
choice> 3
text> A frame as wide as its longest line, so the right edge closes.
width of the text (10 to 70)> 24
padding (0 to 4)> 2
line up (left, centre or right)> centre
+--------------------------+
| A frame as wide as its |
| longest line, so the |
| right edge closes. |
+--------------------------+
1) big letters 2) wrap and line up 3) in a frame 4) quit
choice> 4
Bye.
What was typed (22 lines)
1
Hello EML 2026!
1
eml-p rocks?
2
The quick brown fox jumps over the lazy dog, again and again.
30
left
2
The quick brown fox jumps over the lazy dog, again and again.
20
centre
2
Some words are longer than a line: internationalisation, for one.
15
right
3
A frame as wide as its longest line, so the right edge closes.
24
2
centre
4
Modules
The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.
main.eml(entry)
eml# P077 text banner: big five-row letters, wrapped to the screen; ordinary
# text wrapped to a width and lined up left, centred or right; and a frame
# round it.
import banner
def trim(s):
0 => i
len(s) => j
while i < j and s[i] == " ":
i + 1 => i
while j > i and s[j - 1] == " ":
j - 1 => j
return s[i:j]
def number(s):
if len(s) == 0 or len(s) > 2:
return -1
0 => n
for c in s:
if not (c >= "0" and c <= "9"):
return -1
n * 10 + int(c) => n
return n
def ask(prompt, low, high):
while True:
trim(input(prompt + " (" + str(low) + " to " + str(high) + ")> ")) => a
if a == "":
return -1
number(a) => n
if n >= low and n <= high:
return n
("Type a whole number from " + str(low) + " to " + str(high) + ".") ^0
def ask_text(prompt):
while True:
trim(input(prompt)) => s
if s == "" or len(s) <= 200:
return s
"Up to 200 characters." ^0
def big():
while True:
ask_text("text for the banner> ") => s
if s == "":
"Cancelled." ^0
return
if banner.can_draw(s):
# 13 letters of 5 columns, a space between them, fit in 80.
banner.wrap(s, 13) => parts
for k in [0:len(parts) - 1]:
if k > 0:
"" ^0
for row in banner.big(parts[k]):
row ^0
return
"The big letters are A to Z, 0 to 9, space and ! ? . -" ^0
def ask_how():
while True:
trim(input("line up (left, centre or right)> ")) => h
if h == "":
return ""
if h == "left" or h == "centre" or h == "right":
return h
"Type left, centre or right." ^0
def wrapped():
ask_text("text> ") => s
if s == "":
"Cancelled." ^0
return
ask("width", 10, 80) => w
if w == -1:
"Cancelled." ^0
return
ask_how() => how
if how == "":
"Cancelled." ^0
return
"+" + "-" * (w - 2) + "+" => ruler
ruler ^0
for line in banner.wrap(s, w):
banner.aligned(line, w, how) ^0
ruler ^0
("(the rule marks the width, " + str(w) + " columns)") ^0
def framed():
ask_text("text> ") => s
if s == "":
"Cancelled." ^0
return
ask("width of the text", 10, 70) => w
if w == -1:
"Cancelled." ^0
return
ask("padding", 0, 4) => pad
if pad == -1:
"Cancelled." ^0
return
ask_how() => how
if how == "":
"Cancelled." ^0
return
banner.wrap(s, w) => lines
0 => widest
for line in lines:
if len(line) > widest:
len(line) => widest
[] => placed
for line in lines:
placed + [banner.aligned(line, widest, how)] => placed
for row in banner.boxed(placed, pad):
row ^0
"== Text banner ==" ^0
"Big letters, wrapped and lined-up text, and a frame that fits." ^0
True => running
while running:
"" ^0
"1) big letters 2) wrap and line up 3) in a frame 4) quit" ^0
trim(input("choice> ")) => choice
if choice == "1":
big()
elif choice == "2":
wrapped()
elif choice == "3":
framed()
elif choice == "4":
False => running
else:
"Pick a number from 1 to 4." ^0
"Bye." ^0
Python projection (main.py)
import banner
def trim(s):
i = 0
j = len(s)
while i < j and s[i] == " ":
i = i + 1
while j > i and s[j - 1] == " ":
j = j - 1
return s[i:j]
def number(s):
if len(s) == 0 or len(s) > 2:
return -1
n = 0
for c in s:
if not (c >= "0" and c <= "9"):
return -1
n = n * 10 + int(c)
return n
def ask(prompt, low, high):
while True:
a = trim(input(prompt + " (" + str(low) + " to " + str(high) + ")> "))
if a == "":
return -1
n = number(a)
if n >= low and n <= high:
return n
print("Type a whole number from " + str(low) + " to " + str(high) + ".")
def ask_text(prompt):
while True:
s = trim(input(prompt))
if s == "" or len(s) <= 200:
return s
print("Up to 200 characters.")
def big():
while True:
s = ask_text("text for the banner> ")
if s == "":
print("Cancelled.")
return
if banner.can_draw(s):
parts = banner.wrap(s, 13)
for k in range(0, len(parts)):
if k > 0:
print("")
for row in banner.big(parts[k]):
print(row)
return
print("The big letters are A to Z, 0 to 9, space and ! ? . -")
def ask_how():
while True:
h = trim(input("line up (left, centre or right)> "))
if h == "":
return ""
if h == "left" or h == "centre" or h == "right":
return h
print("Type left, centre or right.")
def wrapped():
s = ask_text("text> ")
if s == "":
print("Cancelled.")
return
w = ask("width", 10, 80)
if w == -1:
print("Cancelled.")
return
how = ask_how()
if how == "":
print("Cancelled.")
return
ruler = "+" + "-" * (w - 2) + "+"
print(ruler)
for line in banner.wrap(s, w):
print(banner.aligned(line, w, how))
print(ruler)
print("(the rule marks the width, " + str(w) + " columns)")
def framed():
s = ask_text("text> ")
if s == "":
print("Cancelled.")
return
w = ask("width of the text", 10, 70)
if w == -1:
print("Cancelled.")
return
pad = ask("padding", 0, 4)
if pad == -1:
print("Cancelled.")
return
how = ask_how()
if how == "":
print("Cancelled.")
return
lines = banner.wrap(s, w)
widest = 0
for line in lines:
if len(line) > widest:
widest = len(line)
placed = []
for line in lines:
placed = placed + [banner.aligned(line, widest, how)]
for row in banner.boxed(placed, pad):
print(row)
print("== Text banner ==")
print("Big letters, wrapped and lined-up text, and a frame that fits.")
running = True
while running:
print("")
print("1) big letters 2) wrap and line up 3) in a frame 4) quit")
choice = trim(input("choice> "))
if choice == "1":
big()
elif choice == "2":
wrapped()
elif choice == "3":
framed()
elif choice == "4":
running = False
else:
print("Pick a number from 1 to 4.")
print("Bye.")
banner.eml
eml# P077 text banner - big letters five rows high, text wrapped to a width
# and lined up left, centred or right, and a frame drawn round it whose
# width comes from the longest line, as in the corpus case
# text-banner-builder - so a wrong count shows as a ragged edge.
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 !?.-" => LETTERS
"abcdefghijklmnopqrstuvwxyz" => SMALL
[
[" ### ", "# #", "#####", "# #", "# #"],
["#### ", "# #", "#### ", "# #", "#### "],
[" ####", "# ", "# ", "# ", " ####"],
["#### ", "# #", "# #", "# #", "#### "],
["#####", "# ", "#### ", "# ", "#####"],
["#####", "# ", "#### ", "# ", "# "],
[" ####", "# ", "# ##", "# #", " ####"],
["# #", "# #", "#####", "# #", "# #"],
["#####", " # ", " # ", " # ", "#####"],
["#####", " # ", " # ", "# # ", " ## "],
["# #", "# # ", "### ", "# # ", "# #"],
["# ", "# ", "# ", "# ", "#####"],
["# #", "## ##", "# # #", "# #", "# #"],
["# #", "## #", "# # #", "# ##", "# #"],
[" ### ", "# #", "# #", "# #", " ### "],
["#### ", "# #", "#### ", "# ", "# "],
[" ### ", "# #", "# # #", "# # ", " ## #"],
["#### ", "# #", "#### ", "# # ", "# #"],
[" ####", "# ", " ### ", " #", "#### "],
["#####", " # ", " # ", " # ", " # "],
["# #", "# #", "# #", "# #", " ### "],
["# #", "# #", "# #", " # # ", " # "],
["# #", "# #", "# # #", "## ##", "# #"],
["# #", " # # ", " # ", " # # ", "# #"],
["# #", " # # ", " # ", " # ", " # "],
["#####", " # ", " # ", " # ", "#####"],
[" ### ", "# ##", "# # #", "## #", " ### "],
[" # ", " ## ", " # ", " # ", " ### "],
[" ### ", "# #", " ## ", " # ", "#####"],
["#### ", " #", " ### ", " #", "#### "],
["# #", "# #", "#####", " #", " #"],
["#####", "# ", "#### ", " #", "#### "],
[" ### ", "# ", "#### ", "# #", " ### "],
["#####", " #", " # ", " # ", " # "],
[" ### ", "# #", " ### ", "# #", " ### "],
[" ### ", "# #", " ####", " #", " ### "],
[" ", " ", " ", " ", " "],
[" # ", " # ", " # ", " ", " # "],
[" ### ", "# #", " ## ", " ", " # "],
[" ", " ", " ", " ", " # "],
[" ", " ", "#####", " ", " "],
] => GLYPHS
def index_in(s, c):
for i in [0:len(s) - 1]:
if s[i] == c:
return i
return -1
def upper(c):
index_in(SMALL, c) => i
if i >= 0:
return LETTERS[i]
return c
def can_draw(text):
for c in text:
if index_in(LETTERS, upper(c)) == -1:
return False
return True
def trim_right(s):
len(s) => j
while j > 0 and s[j - 1] == " ":
j - 1 => j
return s[0:j]
def big(text):
# Five rows: each letter's glyph, one space between letters, and no
# spaces left at the ends of the rows.
["", "", "", "", ""] => rows
for k in [0:len(text) - 1]:
GLYPHS[index_in(LETTERS, upper(text[k]))] => g
for r in [0:4]:
if k > 0:
rows[r] + " " => rows[r]
rows[r] + g[r] => rows[r]
for r in [0:4]:
trim_right(rows[r]) => rows[r]
return rows
def words(s):
[] => out
"" => w
for c in s + " ":
if c == " ":
if w != "":
out + [w] => out
"" => w
else:
w + c => w
return out
def wrap(text, width):
# Greedy wrapping: as many words as fit on each line, one space between
# them; a word longer than the width is cut into pieces that fit.
[] => lines
"" => line
for w in words(text):
while len(w) > width:
if line != "":
lines + [line] => lines
"" => line
lines + [w[0:width]] => lines
w[width:len(w)] => w
if line == "":
w => line
elif len(line) + 1 + len(w) <= width:
line + " " + w => line
else:
lines + [line] => lines
w => line
if line != "":
lines + [line] => lines
return lines
def aligned(line, width, how):
# The line placed in the width: "left", "centre" (an odd space left
# over goes on the right) or "right". Nothing is added after the text.
0 => left
if how == "right":
width - len(line) => left
elif how == "centre":
int((width - len(line) - (width - len(line)) % 2) / 2) => left
return " " * left + line
def boxed(lines, pad):
# The corpus case's frame: as wide as the longest line plus the
# padding on both sides, so the right edge closes exactly.
0 => widest
for line in lines:
if len(line) > widest:
len(line) => widest
widest + pad * 2 => inner
["+" + "-" * inner + "+"] => out
for line in lines:
out + ["|" + " " * pad + line + " " * (inner - pad - len(line)) + "|"] => out
return out + ["+" + "-" * inner + "+"]
Python projection (banner.py)
LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 !?.-"
SMALL = "abcdefghijklmnopqrstuvwxyz"
GLYPHS = [[" ### ", "# #", "#####", "# #", "# #"], ["#### ", "# #", "#### ", "# #", "#### "], [" ####", "# ", "# ", "# ", " ####"], ["#### ", "# #", "# #", "# #", "#### "], ["#####", "# ", "#### ", "# ", "#####"], ["#####", "# ", "#### ", "# ", "# "], [" ####", "# ", "# ##", "# #", " ####"], ["# #", "# #", "#####", "# #", "# #"], ["#####", " # ", " # ", " # ", "#####"], ["#####", " # ", " # ", "# # ", " ## "], ["# #", "# # ", "### ", "# # ", "# #"], ["# ", "# ", "# ", "# ", "#####"], ["# #", "## ##", "# # #", "# #", "# #"], ["# #", "## #", "# # #", "# ##", "# #"], [" ### ", "# #", "# #", "# #", " ### "], ["#### ", "# #", "#### ", "# ", "# "], [" ### ", "# #", "# # #", "# # ", " ## #"], ["#### ", "# #", "#### ", "# # ", "# #"], [" ####", "# ", " ### ", " #", "#### "], ["#####", " # ", " # ", " # ", " # "], ["# #", "# #", "# #", "# #", " ### "], ["# #", "# #", "# #", " # # ", " # "], ["# #", "# #", "# # #", "## ##", "# #"], ["# #", " # # ", " # ", " # # ", "# #"], ["# #", " # # ", " # ", " # ", " # "], ["#####", " # ", " # ", " # ", "#####"], [" ### ", "# ##", "# # #", "## #", " ### "], [" # ", " ## ", " # ", " # ", " ### "], [" ### ", "# #", " ## ", " # ", "#####"], ["#### ", " #", " ### ", " #", "#### "], ["# #", "# #", "#####", " #", " #"], ["#####", "# ", "#### ", " #", "#### "], [" ### ", "# ", "#### ", "# #", " ### "], ["#####", " #", " # ", " # ", " # "], [" ### ", "# #", " ### ", "# #", " ### "], [" ### ", "# #", " ####", " #", " ### "], [" ", " ", " ", " ", " "], [" # ", " # ", " # ", " ", " # "], [" ### ", "# #", " ## ", " ", " # "], [" ", " ", " ", " ", " # "], [" ", " ", "#####", " ", " "]]
def index_in(s, c):
for i in range(0, len(s)):
if s[i] == c:
return i
return -1
def upper(c):
i = index_in(SMALL, c)
if i >= 0:
return LETTERS[i]
return c
def can_draw(text):
for c in text:
if index_in(LETTERS, upper(c)) == -1:
return False
return True
def trim_right(s):
j = len(s)
while j > 0 and s[j - 1] == " ":
j = j - 1
return s[0:j]
def big(text):
rows = ["", "", "", "", ""]
for k in range(0, len(text)):
g = GLYPHS[index_in(LETTERS, upper(text[k]))]
for r in range(0, 5):
if k > 0:
rows[r] = rows[r] + " "
rows[r] = rows[r] + g[r]
for r in range(0, 5):
rows[r] = trim_right(rows[r])
return rows
def words(s):
out = []
w = ""
for c in s + " ":
if c == " ":
if w != "":
out = out + [w]
w = ""
else:
w = w + c
return out
def wrap(text, width):
lines = []
line = ""
for w in words(text):
while len(w) > width:
if line != "":
lines = lines + [line]
line = ""
lines = lines + [w[0:width]]
w = w[width:len(w)]
if line == "":
line = w
elif len(line) + 1 + len(w) <= width:
line = line + " " + w
else:
lines = lines + [line]
line = w
if line != "":
lines = lines + [line]
return lines
def aligned(line, width, how):
left = 0
if how == "right":
left = width - len(line)
elif how == "centre":
left = int((width - len(line) - (width - len(line)) % 2) / 2)
return " " * left + line
def boxed(lines, pad):
widest = 0
for line in lines:
if len(line) > widest:
widest = len(line)
inner = widest + pad * 2
out = ["+" + "-" * inner + "+"]
for line in lines:
out = out + ["|" + " " * pad + line + " " * (inner - pad - len(line)) + "|"]
return out + ["+" + "-" * inner + "+"]