# Translate Text into Matoran

The Matoran Alphabet is the alphabet used by many of the characters in the Bionicle universe.

Your challenge is to create a program or function that takes a string as input and creates an image of the input written in the Matoran alphabet.

The input will only consist of uppercase letters (A-Z), numbers (0-9), and spaces. You may instead take input in lowercase.

## Glyphs

As you can probably notice, the alphabet comprises glyphs made up of circles and straight lines.

For the sake of this challenge, let's define each glyph as a circle of radius 4 units (8 x 8), with some internal designs. The internal designs of letters can be any of:

• Vertical Line at position 2, 4, or 6 (either full length or half)
• Horizontal Line equivalent to above
• Circle of radius 1, centered at [2, 2], [2, 6], [6, 2], or [6, 6]
• Any of the above rotated by 45 degrees about the centre

Note that for some glyphs (notably D, H, and K) only some of the drawn parts have the rotation applied.

Number glyphs work slightly differently; they always have a circle of radius 1 in the centre. For numbers 0 - 5, the glyph has evenly spaced spokes between the circles, starting from the top. For numbers greater than 5, the glyph has an additional circle of radius 2 in the centre, and the number of spokes is 6 less than the number.

Here is a diagram of the glyphs in spec, any glyphs with 45 degree rotations have an orange grid.

For drawing the glyphs, any line stroke in the range (0, 1) can be used. Glyphs can be kerned (space between adjacent glyphs) any amount from 0 - 4 units. Spaces should be 8 units wide (in addition to kerning).

This is a code-golf challenge, so the shortest code in bytes in each language wins.

• I will actually note that (something I did not quite notice while this was in the sandbox) that your description for the construction of these glyphs is not technically enough to explain the construction of the D, H, and K glyphs, however this may be corrected simply by specifying that the 45 degree rotation may be performed at a single arbitrary point in the construction of the glyph, such that some parts of the glyph are drawn before the rotation, and some after the rotation Apr 19 at 3:19
• I did try to explain that the rotation can be applied to any subset of parts of the glyph, but I will clarify. Apr 19 at 3:30

# Java 10, 12911271 1265 bytes

import java.awt.*;a->new Frame(){Graphics G;void o(int x,int y,int s){G.drawOval(x,y,s,s);}void l(int...c){G.drawLine(c[0],c[1],c[2],c[3]);}public void paint(Graphics g){G=g;int x=10,A=20,B=40,C,D=60,E=70,F=80,G=100,H=120,I,J;for(var c:a){C=x+B;I=x+67;J=x+30;if(c>32){o(x,B,F);if(c<58){o(J,E,A);if(c>53){o(x+A,D,B);if(c>56){l(C,B,C,D);l(x+22,90,x+7,G);l(x+58,90,x+73,G);}else{if(c>54)l(C,B,C,D);if(c>55)l(C,G,C,H);}}else{if(c>52){l(C,B,C,E);l(x+2,69,x+31,77);l(x+78,69,x+49,77);l(x+34,87,x+17,112);l(x+46,87,x+63,112);}else if(c==51){l(C,B,C,E);l(x+32,85,x+7,G);l(x+48,85,x+73,G);}else{if(c>48)l(C,B,C,E);if(c>49)l(C,90,C,H);if(c>51){l(x,F,J,F);l(x+50,F,x+F,F);}}}}else{var q=c+"";if("BHUV".contains(q))o(J,B,A);if(c>66&c<69|c==87)o(x+D,E,A);if(c<67|c==72)o(J,G,A);if(c==87)o(x,E,A);if(c>81&c<84|c==F)o(x+50,50,A);if(c==76|c==81)o(x+50,90,A);if(c==74|c==83)o(x+10,90,A);if("DGIJLPR".contains(q))l(C,B,C,H);if(c==71|c==82|c==84)l(C,F,x+F,F);if(c==84)l(C,F,C,H);if(c==75|c==84)l(x,F,C,F);if("KVWXYZ".contains(q))l(C,F,I,52);if("KNVWXY".contains(q))l(C,F,I,108);if("VWXZ".contains(q))l(x+13,108,I,52);if("NVWXY".contains(q))l(x+13,52,I,108);if(c>68&c<71){l(x+5,D,x+75,D);if(c<70)l(x+5,G,x+75,G);}if(c==72|c==77){l(x+A,45,x+A,115);l(x+D,45,x+D,115);}}}x+=F;}}{show();}}


Input as a character-array. Output with a unit being 10 pixels without kerning.

Some example I/O screenshots:

"1357902468":

"KEVIN CRUIJSSEN":

"MATORAN ALPHABET":

import java.awt.*;                   // Required import for Frame and Graphics
a->                                  // Method with char-array parameter and Frame return-type
new Frame(){                       //  Create the Frame
Graphics G;                      //   Put a Graphics instance on class-level
void o(int x,int y,int s){       //   Create a shorter method o for drawOval
G.drawOval(x,y,s,s);}          //    Draw a circle with width/height s with its top-left corner of a virtual surrounding square at position x,y
void l(int...c){                 //   Create a shorter method l for drawLine
G.drawLine(c[0],c[1],c[2],c[3]);}
//    Draw a line from x1,y1 to x2,y2
public void paint(Graphics g){   //   Overwrite the Frame's paint method:
G=g;                           //    Save its Graphics parameter in the class-level variable
int x=10,                      //    Start the x position at 10 (for the left-side border of the Frame)
A=20,B=40,C,D=60,E=70,F=80,G=100,H=120,I,J;
//    Constants to save bytes
for(var c:a){                  //    Loop over the characters c:
C=x+B;I=x+67;J=x+30;         //     Set more constants that use variable x
if(c>32){                    //     If c is NOT a space:
o(x,B,F);                  //      Draw a big surrounding circle
if(c<58){                  //      If c is a digit:
o(J,E,A);                //       Draw the center circle
if(c>53){                //       If c is larger than '5':
o(x+A,D,B);            //        Draw the larger center circle
if(c>56){              //        If c is '9':
l(C,B,C,D);l(x+22,90,x+7,G);l(x+58,90,x+73,G);}
//         Draw its three small lines
else{                  //        Else (it's '6'/'7'/'8'):
if(c>54)             //         If it's '7' or '8':
l(C,B,C,D);        //          Draw the small top vertical line
if(c>55)             //         If it's '8':
l(C,G,C,H);}}      //          Also draw the small bottom vertical line
else{                    //       Else (c is '5' or smaller):
if(c>52){               //        If c is '5':
l(C,B,C,E);l(x+2,69,x+31,77);l(x+78,69,x+49,77);l(x+34,87,x+17,112);l(x+46,87,x+63,112);}
//         Draw its five small lines
else if(c==51){         //        Else-if c is '3':
l(C,B,C,E);l(x+32,85,x+7,G);l(x+48,85,x+73,G);}
//         Draw its three small lines
else{                   //        Else (c is '0'/'1'/'2'/'4'):
if(c>48)              //         If c is larger than '0':
l(C,B,C,E);         //          Draw the top medium vertical line
if(c>49)              //         If c is larger than '1' (thus '2'/'4'):
l(C,90,C,H);        //          Draw the bottom medium vertical line
if(c>51){             //         If c is '4':
l(x,F,J,F);l(x+50,F,x+F,F);}}}}
//          Draw its left/right lines
else{                      //      Else (c is a letter):
var q=c+"";              //       Transform the char to a String
if("BHUV".contains(q))   //       If c is one of BHUV:
o(J,B,A);              //        Draw the top small circle
if(c>66&c<69|c==87)      //       If c is one of CDW:
o(x+D,E,A);            //        Draw the right small circle
if(c<67|c==72)           //       If c is one of ABH:
o(J,G,A);              //        Draw the bottom small circle
if(c==87)                //       If c is W:
o(x,E,A);              //        Draw the left small circle
if(c>81&c<84|c==F)       //       If c is one of PRS:
o(x+50,50,A);          //        Draw the top-right small circle
if(c==76|c==81)          //       If c is L or Q:
o(x+50,90,A);          //        Draw the bottom-right small circle
if(c==74|c==83)          //       If c is J or S:
o(x+10,90,A);          //        Draw the bottom-left small circle
if("DGIJLPR".contains(q))//       If c is one of DGIJLPR:
l(C,B,C,H);            //        Draw the large vertical line
if(c==71|c==82|c==84)    //       If c is one of GRT:
l(C,F,x+F,F);          //        Draw the right horizontal line
if(c==84)                //       If c is T:
l(C,F,C,H);            //        Draw the bottom vertical line
if(c==75|c==84)          //       If c is K or T:
l(x,F,C,F);            //        Draw the left horizontal line
if("KVWXYZ".contains(q)) //       If c is one of KVWXYZ:
l(C,F,I,52);           //        Draw the top-right anti-diagonal
if("KNVWXY".contains(q)) //       If c is one of KNVWXY:
l(C,F,I,108);          //        Draw the bottom-right diagonal
if("VWXZ".contains(q))   //       If c is one of VWXZ:
l(x+13,108,I,52);      //        Draw the bottom-left anti-diagonal
if("NVWXY".contains(q))  //       If c is one of NVWXY:
l(x+13,52,I,108);      //        Draw the top-left diagonal
if(c>68&c<71){           //       If c is E or F:
l(x+5,D,x+75,D);       //        Draw the top horizontal line
if(c<70)               //        If c is E:
l(x+5,G,x+75,G);}    //         Draw the bottom horizontal line
if(c==72|c==77){         //       If c is H or M:
l(x+A,45,x+A,115);l(x+D,45,x+D,115);}}}
//        Draw the left/right vertical lines
x+=F;}}                      //     Increase x by 80 (8 units)
{                                //   And then in an initializer-block:
show();}}                      //    Show the Frame

• Very cool! I look forward to the explanation if it comes Apr 20 at 1:19
• @MatthewJensen I've added an explanation. It's not too exciting tbh. (And I'm sure some mathematical formulas and magic numbers could save some bytes here and there in the if-statements, although I'm not too good at those.) Apr 20 at 7:39
• how did you beat python with java? Apr 20 at 11:24
• @emanresuA You tell me, lol. ;) I had already posted my answer before the Python one. Although I'm sure both can still be golfed some more. Still, I'm indeed a bit surprised as well, since the Python answer uses rotates, which are usually a lot shorter in [graphical-output] challenges. I never use rotations in my Java AWT answers, because it usually would result in double the amount of bytes. Apr 20 at 11:56
• actually reading through this answer more closely, this is a pretty darn good way of doing this TBH, I suppose my method of breaking the glyphs up into components and making a function that draws them by those components, and then using a dict to look things up, isnt quite as compact Apr 20 at 18:46

# Python 3 + pygame, 13651270 1261 bytes

Full program that takes a string from stdin, and displays the glyphs in a window.

Edit -95 bytes: Now shorter than Java!!! I've lost track of what all I golfed, but theres a hell of a lot of constants in here that replace repeated numbers and tuples, and various other stuff. Also note that the \x0cs on line 8 represent literal ASCII formfeeds (codepoint 0C)

Edit: @Steffan pointed out a few mistakes that were breaking code, fixed them.

Edit -9 bytes: Take this Java!! Big thanks to user in chat for helping me golf down the number section

from pygame import*
from math import*
A,c,l,S,d,T,U,V,W,X,Y,Z,I="red",draw.circle,draw.line,Surface,display,(w:=16,4),(x:=32,0),(w,60),(v:=48,60),(x,x),(y:=64,y),(x,y),()
def L(C,L,R,E):
s=S(Y);c(s,A,X,x,1);_={0:(T,V),1:(U,Z),2:((v,4),W),4:(X,Z),5:((4,w),(60,w)),6:((0,x),(y,x)),7:((4,v),(60,v)),8:((0,x),X),9:(X,(y,x))}
for i in C:c(s,A,{1:(v,w),2:(v,v),3:(w,v)}[i],8,1)
for i in L:l(s,A,*_[i])
if R:s=transform.rotate(s,R*45).subsurface(*b'\x0c\x0cKK')
for i in E:l(s,A,*_[i])
return s
def N(n):
s=S(Y);exec(3*"c(s,A,X,%i,1);"%(x,8,n//6*w))
for i in range(a:=n%6):z=lambda r:(x+r*sin(b:=2*pi*i/a),x-r*cos(b));l(s,A,z(8+n//6*8),z(x))
return s
t=input()
s=d.set_mode((y*len(t),y))
for i in range(len(t)):s.blit(L(*{"A":([3],I,1,I),"B":((1,3),I,1,I),"C":([2],I,1,I),"D":([2],I,1,[1]),"E":(I,(5,7),0,I),"F":(I,[5],0,I),"G":(I,(1,9),0,I),"H":((1,3),I,1,(0,2)),"I":(I,[1],0,I),"J":([3],[1],0,I),"K":(I,(4,9),1,[8]),"L":([2],[1],0,I),"M":(I,(0,2),0,I),"N":(I,[1],1,I),"O":(I,I,0,I),"P":([2],[1],0,I),"Q":([2],I,0,I),"R":([1],(1,9),0,I),"S":((1,3),I,0,I),"T":(I,(4,6),0,I),"U":([1],I,1,I),"V":([1],(1,6),1,I),"W":((1,3),(1,6),-1,I),"X":(I,(1,6),1,I),"Y":(I,(1,9),1,I),"Z":(I,[6],1,I),}[t[i]])if t[i].isalpha()else N(int(t[i]))if" "!=t[i]else S(Y),(y*i,0))
d.flip()


Some sample outputs:

HELLO WORLD:

0123456789:

MATORAN TESTING:

## Ungolfed and (somewhat) explained version

from pygame import*
from math import*
W="red"
def Letter(circles, lines, rot, extra): ## Function that contructs a letter glyph
S = Surface((64,64)) ## Surface to draw on
draw.circle(S,W,(32,32),32,1) ## Exterior circle
for i in circles: ## Draw the small circles
draw.circle(S,W,{0:(16,16),1:(48,16),2:(48,48),3:(16,48)}[i],8,1) ## This dict maps 0,1,2,3 to the different circle positions, clockwise from top left. Circle 0 is not used in the golfed version.
for i in lines: ## Draw the lines
draw.line(S,W,*{0:((16,4),(16,60)),1:((32,0),(32,64)), ## This dict maps numbers to the coords for various line segments.
2:((48,4),(48,60)),3:((32,0),(32,32)),4:((32,32),(32,64)), ## 0,1,2 are full length vertical lines, 4,5 are half length vertical lines
5:((4,16),(60,16)),6:((0,32),(64,32)),7:((4,48),(60,48)), ## 5,6,7 are full length horizontal lines, 8,9 are half length horizontal lines
8:((0,32),(32,32)),9:((32,32),(64,32))}[i]) ## 3 and 8 are not used in the golfed version
if rot: ## rotate the glyph 45 degrees, if necessary
S=transform.rotate(S,R*45).subsurface((12,12,75,75)) ## the R*45 is used in the golfed version to sometimes rotate the other direction, which allowed the removal of some circle/line keys from the dicts
for i in extra: ## draw the extra line segments necessary, names the same as above for consistency
draw.line(S,W,*{0:((16,4),(16,60)),1:((32,0),(32,64)),
2:((48,4),(48,60)),8:((0,32),(32,32))}[i])
return S ## return the surface
def Number(n): ## contructs a number glyph from its value
S = Surface((64,64)) ## Contruct a surface to draw on
draw.circle(S,W,(32,32),32,1) ## Exterior circle
draw.circle(S,W,(32,32),8,1) ## Central circle
draw.circle(S,W,(32,32),16*(n//6),1) ## Larger central circle, radius 0 if unneeded (radius 0 == nothing drawn)
for i in range(n%6): ## draw the spokes
a,r=(2*pi*i)/(n%6),8+n//6*8 ## current angle and the radius of the central circle
draw.line(S,W, ## draw the line, with appropriate endpoints
(32+sin(a)*r,32-cos(a)*r),
(32+sin(a)*32,32-cos(a)*32)
)
return S ## return the surface
T=input().upper() ## take input from stdin, .upper() to make ungolfed version more friendly
s=display.set_mode((64*len(T),64)) ## setup the display wide enough for the input text
for i in range(len(T)): ## loop through the characters
s.blit( ## blit (copy) the glyph surface onto the display
Letter(*{"A":((3,),(),1,()),"B":((1,3),(),1,()),"C":((2,),(),1,()), ## if its a letter, this (somewhat scary) dict maps
"D":((2,),(),1,(1,)),"E":((),(5,7),0,()),"F":((),(5,),0,()), ## the letter to the correct args for Letter()
"G":((),(1,9),0,()),"H":((1,3),(),1,(0,2)),"I":((),(1,),0,()),
"J":((3,),(1,),0,()),"K":((),(4,9),1,(8,)),"L":((2,),(1,),0,()),
"M":((),(0,2),0,()),"N":((),(1,),1,()),"O":((),(),0,()),
"P":((1,),(1,),0,()),"Q":((2,),(),0,()),
"R":((1,),(1,9),0,()),"S":((1,3),(),0,()),"T":((),(4,6),0,()),
"U":((1,),(),1,()),"V":((1,),(1,6),1,()),
"W":((0,2),(1,6),1,()),"X":((),(1,6),1,()),
"Y":((),(1,9),1,()),"Z":((),(6,),1,()),}[T[i]])
if T[i].isalpha() ## only send the char to Letter() if it is a letter
else Number(int(T[i])) ## otherwise send it to Number() as an int
if T[i]!=" " ## but if its a space, don't send it to either function
else Surface((64,64)), ## instead just make a new, empty surface
(64*i,0)) ## place at the right position
display.flip() ## render all chars

• A few ideas: all of the tuples containing only constant integers can probably be replaced by byte strings (for example (48,48) -> b'00'). In some cases \n  can be replaced by a ; to save a byte. You can probably merge the last two dictionaries in L, as all shared keys point to the same values
– ovs
Apr 20 at 8:06
• Some minor things to golf: you can remove the space at .isalpha() else; 16*(n//6) can be n//6*16; (2*pi*i) can be without parenthesis; if t[i]!=" "else can be if" "!=t[i]else to get rid of the space. (And as mentioned in the comment above, on some lines you can change the newline+space to a ;.) Apr 20 at 12:05
• @ovs those are some good thoughts, I actually have a somewhat more golfed version that assigns a lot of repeated tuples to constants, but thats a good shout about using bytestrings, that I dont think I wouldve thought of, and I cant believe I didnt think of merging the dicts for L (that will make the shared dict a tiny bit longer though, as IIRC line 8 is not in the bigger dict) Apr 20 at 18:18
• @KevinCruijssen good thoughts on cleaning up some whitespace, I think I figured a few of those out myself, but good thoughts about that third one in the space-checking Apr 20 at 18:19
• this part "K":(I,(4,9),1,([8]) errors Apr 21 at 16:19

# JavaScript + HTML, 582 bytes

-many, many bytes thanks to tsh!

Changed it to modify document.body to better match this other JS + HTML answer.

s=>document.body.innerHTML+=<svg viewBox=5,-5,${[c,l]=['<circle r=cx=cy=','<line y2=y1=x2=x1='].map(n=>(...a)=>n[$](/=/g,_=>=${a.pop()} )+transform=rotate(${R*45}) />),s.length}0,10 stroke=red stroke-width=.2>+s.toLowerCase()[$='replace'](/./g,x=>'<g transform=translate(10)>'+(x>' '&&c(R=0,4)+(1/x?[...Array(L=x%6)].map(_=>(R+=8/L,l(-4)))+[x>5&&c(2)]+c(1):'aC1bC1C5cC7dC7LeE2E6fE6gI6LhC1C5E4EiLjC2LkI2I5I7lC0LmE4EnL3pC6LqCrC6I6LsC2C6tL2IuC5vC5L1L3wC3C7L1L3xL1L3yL3I5zL1'.match(x+'([0-Z]+)')?.[1][$](/..?/g,([z,r])=>(R=r,{C:c(2,2,1),L:l(-4,4),I:l(4),E:l(2,2,-3.5,3.5)}[z])))))


f=
s=>document.body.innerHTML+=<svg viewBox=5,-5,${[c,l]=['<circle r=cx=cy=','<line y2=y1=x2=x1='].map(n=>(...a)=>n[$](/=/g,_=>=${a.pop()} )+transform=rotate(${R*45}) />),s.length}0,10 stroke=red stroke-width=.2>+s.toLowerCase()[$='replace'](/./g,x=>'<g transform=translate(10)>'+(x>' '&&c(R=0,4)+(1/x?[...Array(L=x%6)].map(_=>(R+=8/L,l(-4)))+[x>5&&c(2)]+c(1):'aC1bC1C5cC7dC7LeE2E6fE6gI6LhC1C5E4EiLjC2LkI2I5I7lC0LmE4EnL3pC6LqCrC6I6LsC2C6tL2IuC5vC5L1L3wC3C7L1L3xL1L3yL3I5zL1'.match(x+'([0-Z]+)')?.[1][$](/..?/g,([z,r])=>(R=r,{C:c(2,2,1),L:l(-4,4),I:l(4),E:l(2,2,-3.5,3.5)}[z])))))

;

[
'Hello World',
'Matthew Jensen',
'Matoran Alphabet',
'ABCDEFGHIJ',
'KLMNOPQRST',
'UVWXYZ',
'0123456789',
].forEach(text => {
f(text);
//document.body.appendChild(document.createElement('div')).innerHTML = f(text);
});
svg {
background-color: black;
height: 64px;
}

Explained a bit

s => document.body.innerHTML += <svg viewBox=5,-5,${ // add a new SVG element to doc body [c, l] = [ // define function to generate circle and line '<circle r=cx=cy=', '<line y2=y1=x2=x1=' ].map(n => (...a) => // map each above string to function to assign args (reverse order) n[$ /* = 'replace' */ ](/=/g, _ => =${a.pop()} ) + transform=rotate(${R * 45}) />), // apply 1/8 rotations by global R
s.length
}0,10 stroke=red stroke-width=.2> +
s.toLowerCase()[$= 'replace'](/./g, x => // replace each char with its svg '<g transform=translate(10)>' + // begin new group (nested in previous char, idea from tsh!) (x > ' ' && c(R = 0, 4) + // if not a space, draw the large circle (1 / x ? // if char is number [...Array(L = x % 6)] // draw line segments .map(_ => (R += 8 / L, l(-4))) + [x > 5 && c(2)] + // draw r=2 circle if >5 c(1) // draw r=1 circle : // otherwise char is letter 'aC1bC1C5cC7dC7LeE2E6fE6gI6LhC1C5E4EiLjC2LkI2I5I7lC0LmE4EnL3pC6LqCrC6I6LsC2C6tL2IuC5vC5L1L3wC3C7L1L3xL1L3yL3I5zL1' .match(x + '([0-Z]+)')?.[1] // find code for letter [$ /* = 'replace' */ ](/..?/g, ([z, r]) => ( // for each instruction [shape, rotation]
R = r, // set global rotation to r
{ // look up instruction
C: c(2, 2, 1),
L: l(-4, 4),
I: l(4),
E: l(2, 2, -3.5, 3.5)
}[z]
)
)
)
)
)

• save some bytes
– tsh
Apr 21 at 2:40
• This is not generating "SVG". You may claim it as "SVG in HTML" or simply "HTML". As it lacks quotes on attribute and becomes a invalid XML; xmlns is also required for SVG.
– tsh
Apr 21 at 3:13
• Fair point, I'll update. Nice byte saving too! Apr 21 at 3:46
• 580bytes: s=><svg viewBox=5,-5,${[c,l]=['<circle r=cx=cy=','<line x1=y1=x2=y2='].map(n=>(...a)=>n[$](/=/g,_=>=${a.shift()} )+transform=rotate(${R*45}) />),s.length}0,10 stroke=red stroke-width=.2>+s.toLowerCase()[$='replace'](/./g,x=>'<g transform=translate(10)>'+(x>' '&&c(4,R=0)+(1/x?[...Array(L=x%6)].map(_=>l(0,-4,0,0,R=i++*8/L),i=0)+[x>5&&c(2)]+c(1):'aC1bC1C5cC7dC7LeE2E6fE6gI6LhC1C5E4EiLjC2LkI2I5I7lC0LmE4EnL3pC6LqCrC6I6LsC2C6tL2IuC5vC5L1L3wC3C7L1L3xL1L3yL3I5zL1'.match(x+'([0-Z]+)')?.[1][$](/..?/g,z=>({C:c(1,2,2,R=z[1]),L:l(0,-4,0,4),I:l(0,0,0,4),E:l(2,-3.5,2,3.5)})[z[0]]))))
– tsh
Apr 21 at 7:50

# Perl 5 + -M5.10.0 -lF, 1017 bytes

sub d{unpack"B*",pop}sub a{$A[$_].="@_"eq''?0 x8:("@_"=~/.{17}/g)[$_]for 0..16}sub z{0 x pop}a{O,($x=d"........\$.............0........0...."),A,$a=$x|z(194).($C=d"....D.\".."),U,$u=$x|z(24).$C,B,$b=$a|$u,C,$c=$x|z(114).$C,I,$i=$x|z(8).($l=d"..@. .............")|0 x($-=144).$l,D,$c|$i,F,$f=$x|z(70).($h=1x13),E,$f|z(206).$h,G,$g=$i|0 x$-.($H=1x9),M,$m=$x|z(38).($k=(1 .z 16)x13)|z(46).$k,H,$m|$b,J,$i|z(173).$C,K,$x|z(64).($y=d".."x6)|0 x$-.($Y=d".. ........ ")|z(135).$H,Q,$q=$x|z(181).$C,L,$i|$q,N,$n=$x|z(54).$Y|0 x$-.$Y,P,$p=$i|z(45).$C,R,$p|$g,S,$x|z(173).$C|z(45).$C,T,$g=$x|0 x$-.$H|z(135).$H|0 x$-.$l,Y,$z=$n|z(64).$y,X,$X=$z|0 x$-.$y,V,$X|$u,W,$X|z(104).$C|z(114).$C,Z,$x|z(64).$y|0 x$-.$y,0,$Z=$x|z(109).$C,1,$O=$Z|z(25).($L=d"..@. ...."),2,$T=$O|z(195).$L,3,$O|(z(174).d"...0"),4,$T|z(137).1111100000 x2,5,$O|(z(86).d".......0D......D..0."),6,$s=$x|(z(74).d".P....QF(..Q....P."),7,$S=$s|(z(25).($w=d"..@. ")),8,$S|z(229).$w,9,$S|z(180).(z(9).11)x2}->{$_}for@F;$-=y/ //c;say"P1 ",17*$-+8*(@F-$-)," 17 @A" Try it online! ## Explanation The main functionality of this answer utilises a lookup table to store information about each letter we translate, this lookup table is built up from previous letters and augmented using stringwise-OR operations. We start with O which is just a circle. Using the PBM format, this is stored as: 00000111111100000 00011000000011000 00100000000000100 01000000000000010 01000000000000010 10000000000000001 10000000000000001 10000000000000001 10000000000000001 10000000000000001 10000000000000001 10000000000000001 01000000000000010 01000000000000010 00100000000000100 00011000000011000 00000111111100000  From here, A (for example) is augmented by ORing the string:  1110000000000000 10001000000000000 10001000000000000 10001000000000000 01110  with a specific number of leading 0s (194 for A), so as to place this at the right position. The same technique is used to build the other letters and numbers, sometimes using the result of ORing two other letters to get the required output. The binary strings are stored using Perl's pack functionality and the data is output by repeatedly appending each required line from the letters to the list @A's indices 0..16. All the glyphs are hand-made by eye, so please shout if this is not to your liking! # HTML + JS + SVG, 577575571552545544522511 497 bytes t=>[...t,0].map(c=>document.body.innerHTML+=c?<svg viewBox=-4,-4,8,8 width=50 fill=#fff stroke=#000 stroke-width=.1>+(c>" "&&['D',..."Y Y] _ _ PT T J RVY]  Z IKN X RV c = ^ X J^ Z^ Lb ] ]ac [_ac ac Ic a".split [parseInt(c,36)-10]||()*+,${c>5?'B':''}A].map(u=><path d=${M57A5,57,177,5A5,5777,5784 M6,2H-6 M2,1939184v484.replace(/./g,k=>[x=(y=u.charCodeAt())&7,3**.5*2,',0',' M0,-4v','A1,1,0,0,0,2,'][k-5]||k).split [y>>3&7]} transform=rotate(${y<64?x*360/(c%6):45*x}) />)):'<p>')


Or 487 bytes if you don't care about separating the outputs of different calls with <p>. (Replacing the page contents is longer.)

t=>[...t].map(c=>document.body.innerHTML+=<svg viewBox=-4,-4,8,8 width=50 fill=#fff stroke=#000 stroke-width=.1>+(c>" "&&['D',..."Y Y] _ _ PT T J RVY]  Z IKN X RV c = ^ X J^ Z^ Lb ] ]ac [_ac ac Ic a".split [parseInt(c,36)-10]||()*+,${c>5?'B':''}A].map(u=><path d=${M57A5,57,177,5A5,5777,5784 M6,2H-6 M2,1939184v484.replace(/./g,k=>[x=(y=u.charCodeAt())&7,3**.5*2,',0',' M0,-4v','A1,1,0,0,0,2,'][k-5]||k).split [y>>3&7]} transform=rotate(${y<64?x*360/(c%6):45*x}) />)))  Apparently, I had a very similar idea to @Matthew Jensen about SVG being much shorter than the other options. However, I took a pretty different approach. I think this is finally getting pretty close to optimal with my current approach. If only I had a language with JS semantics but Python builtins, like int(), str.translate, oct, str[:], … On my browser (Firefox 100 on Linux), the 0 and 6 render correctly, because an invalid transform hides SVG elements. I'm not sure if this is the case everywhere. The output may be wrapped on small screens or if your Stack Snippet is not fullscreen. (You can reduce width=50 to fix this.) renderMatoran = t=>[...t,0].map(c=>document.body.innerHTML+=c?<svg viewBox=-4,-4,8,8 width=50 fill=#fff stroke=#000 stroke-width=.1>+(c>" "&&['D',..."Y Y] _ _ PT T J RVY]  Z IKN X RV c = ^ X J^ Z^ Lb ] ]ac [_ac ac Ic a".split [parseInt(c,36)-10]||()*+,${c>5?'B':''}A].map(u=><path d=${M57A5,57,177,5A5,5777,5784 M6,2H-6 M2,1939184v484.replace(/./g,k=>[x=(y=u.charCodeAt())&7,3**.5*2,',0',' M0,-4v','A1,1,0,0,0,2,'][k-5]||k).split [y>>3&7]} transform=rotate(${y<64?x*360/(c%6):45*x}) />)):'<p>')

for (const text of ["ABCDEFGHIJ","KLMNOPQRST", "UVWXYZ", "0123456789", "CODE GOLF 101"]) {
renderMatoran(text);
}

### Explanation

t =>
[...t, 0].map(c =>  /* for each char c (plus terminator) ... */
document.body.innerHTML +=  /* the HTML parser will enjoy this... */
c ?                         /* handle terminator */
<svg
viewBox=-4,-4,8,8         /* we want nice and short coords */
width=50                  /* specify some width so the browser */
/* puts our svgs next to each other */
fill=#fff stroke=#000
stroke-width=.1           /* much cheaper than big coords */
> +
(c > " " &&  /* yield false if space (gets dumped in the SVG, */
/* but it does not care about stray strings) */
['D',    /* everything has a big circle */
...      /* convert rest to array elements */
"Y Y] _ _ PT T J RVY]  Z IKN X RV c = ^ X J^ Z^ Lb ] ]ac [_ac ac Ic a"
.split [parseInt(c,36)-10] /* for letters: take corresponding item */
/* from the data string */
|| ()*+,${c>5?'B':''}A /* for numbers: generate string with */ /* 5 spokes and 1-2 circles */ ].map(u => /* for each char u in the instructions... */ <path d=${  /* svg path black magic */
M57A5,57,177,5A5,5777,5784 M6,2H-6 M2,1939184v484
.replace(/./g, k => /* map each character k to... */
[
x = (y = u.charCodeAt()) & 7,  /* 5 -> instruction arg */
3 ** .5 * 2,                   /* 6 -> 2*sqrt(3) */
',0',                          /* 7 */
' M0,-4v',                     /* 8 */
'A1,1,0,0,0,2,'                /* 9 */
][k-5] || k                      /* everything else to itself */
).split [y>>3&7]  /* grab correct instruction */
}
transform=rotate(${ y<64 ? x*360/(c%6) /* rotation for spokes */ : 45*x /* rotation for everything else */ }) /> )) /* just dump the array in the string, the parser will eat the */ /* useless commas :) */ : '<p>' /* make sure each call goes on its own line */ )  • You can save 2 bytes by changing both .innerHTML's to [H='innerHTML'] and [H] Apr 21 at 21:52 • Great job on breaking 500 bytes! Apr 22 at 2:08 # BBC BASIC, 395 385 bytes I.s$
F.i=1TOLENs$c=ASCM.s$,i,1)MOD64IFc=32N.
ORIGIN80*i,80m=ASCM."CBDFFDFRFATETsDFDF?UDxhxsI",c,1)*95+ASCM."!~R8Kg\SJ?bs9?dD.VS{bN|PEu",c,1)t=m MOD2*20n=8m /=2v=t*7/4w=40-t
IFc>47n=c MOD6+1E-9m=728+(n<1)
a=PI*2/n
F.j=0TOa*n S.a:u=m MOD3m/=3y=COSj:x=SINj
IFu<1CIRCLEx*28,y*28,10
IFu>1L.x*t-y*v,y*t+x*v,x*w+y*v,y*w-x*v
N.F.k=5-c DIV27*2TO5j=k MOD5+1MOVE0,0PLOT155-j MOD2*10,0,40>>j/2N.N.


Explanation

For letters, each of the 8 positions from N through E, S and W to NW can be either empty space, a line or a small circle. Additionally, the lines within each letter are usually radial, but for certain letters EFHM they are at right angles. The information for each letter is stored as an 8 digit ternary number, which is multiplied by 2 and the final bit indicates whether lines are radial or at right angles. Encoding is as follows to minimise the range of codes (there is never a circle in the NW position)

  MAIN ENCODING         LINE DIRECTION ENCODING
0 = circle              0 = radial
1 = space               1 = at right angles to radial
2 = line


For digits, the number of spokes is reduced to the ASCII code mod 6, and it is assumed that all spaces are lines with the constant 728. BBC BASIC FOR loops are always excecuted at least once, so where the ASCII code mod 6 is zero, the plotting of the first spoke is suppressed by subtracting 1 from the constant. (n<1) evaluates to TRUE which is represented in BBC BASIC by -1.

A radius of 40 was selected for each character. This is because of the well known approximations 40*SIN45=28 and 40*SIN60=35

Ungolfed code

  INPUT s$:REM input from stdin FOR i=1 TO LEN(s$)                           :REM for each character in input
c=ASC(MID$(s$,i,1))MOD64                   :REM convert to ASCII code MOD 64 so that "A"=1
IF c=32NEXT                                :REM if it is a space do nothing
ORIGIN 80*i,80                               :REM move graphics origin to the cente of the current letter
m=ASC(MID$("CBDFFDFRFATETsDFDF?UDxhxsI",c,1))*95+ASC(MID$("!~R8Kg\SJ?bs9?dD.VS{bN|PEu",c,1))
t=m MOD2*20                                  :REM use the 2 magic strings to lookup the letter data m
m/=2                                         :REM t indicates if lines are radial. Extract t and divide by 2
v=t*7/4                                      :REM for nonradial lines, tangential offset of 20*7/4=35 is needed
n=8                                          :REM 8 spokes for letters
IF c>47 n=c MOD 6+1E-9:m=728+(n<1)           :REM if character is a digit (c>47), there are c MOD6 spokes
a=PI*2/n                                     :REM calculate angle between spokes (+1E-9 on previous line avoids division by 0)
FOR j =0 TO a*n STEP a                         :REM iterate through spokes
u=m MOD 3: m/=3                              :REM extract data u for current spoke then divide m by 3 for the next one
y=COSj:x=SINj                                :REM calculate (x,y) vector for radial direction of current spoke
IF u<1 CIRCLE x*28,y*28,10                   :REM if u=0 draw a circle instead of a spoke
IF u>1 LINE x*t-y*v,y*t+x*v,x*w+y*v,y*w-x*v  :REM if u=2 draw a spoke starting at radius t=0 and ending at radius w=40
NEXT                                           :REM if t=20 draw a nonradial line instead from -v to v passing through radius t=w=20
FOR k=5-c DIV27*2TO5                         :REM iterate through circles: c>=54 k starts at 1; c>=27 k starts at 3, c<2 k starts at 5
j=k MOD 5+1                                  :REM order of circles: 1+1=2, 2+1=3 (digits 6789); 3+1=4 4+1=5 (all digits); 0+1=1 (all symbols)
MOVE 0,0:PLOT 155-j MOD2*10,0,40>>j/2        :REM clear the background for even number circles, draw the circle for odd number circles
NEXT                                           :REM radius 40>>(2/2 or 3/2)=20, 40>>(4/2 or 5/2)=10, 40>>(1/2) = 40
NEXT                                         :REM repeat for next character


# C (GCC), 871 bytes

#define Q(X,Y)(r=(X)*(X)+(Y)*(Y))
#define S(M)(y&M?-1:1)
#define C;for(y=1;y<16;y*=2)c|=x&y<<
#define A labs
int T[]={0,1,3,1,15,1,0,1,3,1,8192,12288,32768,32771,768,256,11,15360,3,131075,164,65539,3072,48,0,262147,65536,262155,393216,14,4096,4336,49392,240,176,192};main(i,v,j,k,x,r,y,n,c,w)char**v;{w=90*strlen(v[1]);printf("P5 %d %d 1 ",w,79);for(i=-40;i++<39;)for(j=-1;++j<w;putchar(c)){c=0;k=j%90-40;n=v[1][j/90];if(n-32){c|=Q(i,k)<1600&r>1400;x=T[n-48-7*(n>64)]C 0&&A(y&3?k:i)<2&k<40&(y&3?i:k)*S(5)>=0 C 4&&A(k)<28&A(i+k*S(3))<2&k*S(5)>=0 C 8&&A((y&3?i:k)-20*S(5))<2&A(i)<34&A(k)<34 C 12&&Q((y&3?i:k)-30*S(5),y&3?k:i)<99&r>63 C 16&&Q(i+20*S(3),k+20*S(5))<99&r>63;if(n<58){for(y=2;A(n-54)==3&&y--;)c|=A(1.73*i+k*S(y))<3&i>0&A(k)<34;for(y=2;n==53&&y--;)c|=A(3.08*i+k*S(y))<5&i<0&A(k)<39,c|=A(.73*i-k*S(y))<2&i>0&A(k)<23;if(n>53)c=(c|r<399)&r>323;c=(c|r<99)&r>63;}}}}


Attempt This Online!

Input is given via argv[1]. Outputs a PGM image.