aassii ggsgggggggggggggg    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaasaa is  ggggggam sa  mmuuuuu
sssssaaaaasssgggggggggggggggaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaiaai    gggggaa  aa gmmmmuuu
aassssaaaaaaaaaasssssgggggg aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaimai    gggggga  aa ggmmmmmmu
ggiiiiii   gggggggggggggggg                                   ai    ggggggag aaggggmmmmmmm
         g gggggggggggggggg  Debug Drawing Text using Lines   si   ggggggag aaggggammmmmmm
aaaassssss           aaaaaa                                   igs gg gggag aagggggammmmmmm
aaaaasssss                  aaaammammmmmmm ssiaaaaaaaaaaaamaaiisa   ggggg aagggggaammmmmmm
sssggii   ssssssssssss      aaasmmm     ii   gaaaaaaaaaaamaaiiaas gggggg aagggggaaasmmmmmm
si        aaassssss         maaaaaasaassaa igssiiussammgsssssamppssaaaaggggggas  a aaaa ii

Debug Drawing Text using Lines

08/04/2025

It's more than once in my life that I've wanted to debug draw some text in a game scene, but for whatever reason the game engine I'm using either doesn't support debug drawing text directly in the scene, makes it unnecessarily difficult, or doing so interferes with the rest of the rendering in a nasty way.

Yet most game engines do support fairly nice and efficient debug drawing of lines, often supporting some kind of thickness, depth testing, and sometimes things like transparency too.

I thought it would probably be possible to draw text using just line rendering, figuring I could build a basic look-up table that mapped character codes to a series of line segments. I'm certain this isn't a new idea, but surprisingly I couldn't find much after some digging around online.

Well here's the look-up table(s) I ended up generating to do this. The line segments roughly follow the shapes of Consolas hence Consolines.

static const char consolines_nums[94] = {
     2,  6,  4, 10, 15, 16,  4,  5,  5,  3,  2,  3,  1,  1,  1, 13,  3,  6,
    11,  4,  8, 13,  2, 14, 15,  2,  4,  2,  2,  2,  7, 28,  3, 12, 10,  9,
     4,  3, 11,  3,  3,  5,  4,  2,  4,  3, 12,  7, 15,  8, 12,  2,  6,  2,
     4,  2,  3,  3,  3,  1,  3,  2,  1,  1, 11, 10,  8, 10, 12,  5, 26,  6,
     4,  6,  4,  3, 11,  6, 10,  9,  9,  5, 10,  5,  6,  2,  4,  2,  4,  3,
    10,  1, 10,  7
};

static const int consolines_offsets[94] = {
      0,   2,   8,  12,  22,  37,  53,  57,  62,  67,  70,  72,  75,  76,
     77,  78,  91,  94, 100, 111, 115, 123, 136, 138, 152, 167, 169, 173,
    175, 177, 179, 186, 214, 217, 229, 239, 248, 252, 255, 266, 269, 272,
    277, 281, 283, 287, 290, 302, 309, 324, 332, 344, 346, 352, 354, 358,
    360, 363, 366, 369, 370, 373, 375, 376, 377, 388, 398, 406, 416, 428,
    433, 459, 465, 469, 475, 479, 482, 493, 499, 509, 518, 527, 532, 542,
    547, 553, 555, 559, 561, 565, 568, 578, 579, 589
};

static const int consolines_lines[596] = {
    0x701F3C1F, 0x2A1F281F, 0x59135206, 0x63175913, 0x6D116317, 0x59305223,
    0x63345930, 0x6D2E6334, 0x563A5608, 0x3B373B05, 0x6A172610, 0x26276A2E,
    0x27222909, 0x2D2F2722, 0x35342D2F, 0x3E313534, 0x510F3E31, 0x590B510F,
    0x620E590B, 0x681A620E, 0x6730681A, 0x19187425, 0x26077038, 0x6D096105,
    0x70156D09, 0x671D7015, 0x5B1B671D, 0x55125B1B, 0x59085512, 0x61055908,
    0x3D263122, 0x40323D26, 0x373A4032, 0x2B38373A, 0x252F2B38, 0x2925252F,
    0x31222925, 0x2538590E, 0x55265F2B, 0x610D590E, 0x6A12610D, 0x6E1C6A12,
    0x6B266E1C, 0x662A6B26, 0x5F2B662A, 0x3F085526, 0x2A282519, 0x290F2519,
    0x2F09290F, 0x37062F09, 0x3F083706, 0x39332A28, 0x46343933, 0x6E1D6621,
    0x5D226621, 0x541A5D22, 0x5210541A, 0x5C1B722B, 0x49165C1B, 0x37164916,
    0x271B3716, 0x102B271B, 0x5C247214, 0x49295C24, 0x37294929, 0x27243729,
    0x10142724, 0x70204620, 0x65324E0D, 0x650E4F32, 0x291F5A1F, 0x42074238,
    0x2423301D, 0x191E2423, 0x1310191E, 0x422D4212, 0x2A1F2C1F, 0x70301A0C,
    0x5A35360A, 0x4A08360A, 0x5F0D4A08, 0x66145F0D, 0x6A206614, 0x652C6A20,
    0x5A35652C, 0x46375A35, 0x31324637, 0x2B2B3132, 0x261F2B2B, 0x2B13261F,
    0x360A2B13, 0x2735270D, 0x6A222722, 0x5E0C6A22, 0x270B2735, 0x502F270B,
    0x5C30502F, 0x672A5C30, 0x6B1D672A, 0x630D6B1D, 0x2625270C, 0x2F2F2625,
    0x38332F2F, 0x40313833, 0x49224031, 0x522C4922, 0x5C2F522C, 0x652B5C2F,
    0x6A1D652B, 0x670E6A1D, 0x49164922, 0x37053739, 0x262B6A2B, 0x6A233705,
    0x6A2B6A23, 0x680F6830, 0x4A0F680F, 0x4A274A0F, 0x270D261F, 0x2E2E261F,
    0x3A322E2E, 0x442F3A32, 0x4A27442F, 0x671E6930, 0x5E12671E, 0x4E0C5E12,
    0x460B4E0C, 0x360C460B, 0x460B4D1D, 0x2914360C, 0x25212914, 0x2A2E2521,
    0x35352A2E, 0x43333535, 0x4C294333, 0x4D1D4C29, 0x6935690A, 0x26156935,
    0x67125C0C, 0x6B206712, 0x662E6B20, 0x5C32662E, 0x522E5C32, 0x3E0E522E,
    0x350B3E0E, 0x2813350B, 0x261F2813, 0x2A2D261F, 0x32332A2D, 0x3C313233,
    0x53103C31, 0x5C0C5310, 0x2920270D, 0x322D2920, 0x3E32322D, 0x4C333E32,
    0x4C334529, 0x5F304C33, 0x67295F30, 0x6A1E6729, 0x66126A1E, 0x5E0B6612,
    0x550A5E0B, 0x4A0D550A, 0x44144A0D, 0x421E4414, 0x4529421E, 0x531F561F,
    0x291F2C1F, 0x531F561F, 0x23242E1E, 0x181E2324, 0x1311181E, 0x420D262E,
    0x5C2D420D, 0x4C354C0B, 0x380A3835, 0x42312610, 0x5C114231, 0x3B1B4A1B,
    0x4D2A4A1B, 0x59304D2A, 0x622D5930, 0x6B22622D, 0x6E146B22, 0x271B2A1B,
    0x0F25122D, 0x0E1C0F25, 0x12100E1C, 0x1F071210, 0x34041F07, 0x4B063404,
    0x5E0C4B06, 0x6C185E0C, 0x70246C18, 0x6D2F7024, 0x65366D2F, 0x563A6536,
    0x483B563A, 0x3538483B, 0x2B333538, 0x292E2B33, 0x2C29292E, 0x37282C29,
    0x2D203728, 0x4F2B3728, 0x53274F2B, 0x54225327, 0x4E195422, 0x41154E19,
    0x32144115, 0x2B173214, 0x291B2B17, 0x2D20291B, 0x6A1F2606, 0x26396A1F,
    0x3832380D, 0x6A0D260D, 0x672B6A0D, 0x490D4924, 0x260D292C, 0x3032292C,
    0x39353032, 0x40323935, 0x49244032, 0x502E4924, 0x5932502E, 0x61305932,
    0x672B6130, 0x26272A35, 0x29182627, 0x30102918, 0x390C3010, 0x460A390C,
    0x560C460A, 0x6112560C, 0x691E6112, 0x6A27691E, 0x66356A27, 0x690A260A,
    0x6920690A, 0x260A2620, 0x632C6920, 0x5933632C, 0x49375933, 0x39344937,
    0x2E2E3934, 0x26202E2E, 0x260F2632, 0x690F260F, 0x6932690F, 0x490F4931,
    0x6A102510, 0x6A326A10, 0x4810482F, 0x48354823, 0x28354835, 0x6A276635,
    0x67196A27, 0x5D0E6719, 0x4A085D0E, 0x3C094A08, 0x300E3C09, 0x2819300E,
    0x26252819, 0x28352625, 0x6B0A240A, 0x4936490A, 0x24366B36, 0x2633260C,
    0x6933690C, 0x261F691F, 0x692D690E, 0x312D692D, 0x2A25312D, 0x261B2A25,
    0x2A0D261B, 0x6A0D250D, 0x49176A32, 0x25334917, 0x490D4917, 0x26126A12,
    0x26342612, 0x6A0B2507, 0x3F1F6A0B, 0x6A353F1F, 0x25386A35, 0x6B0C250C,
    0x24356B0C, 0x6A352435, 0x662D6A20, 0x5E33662D, 0x4C385E33, 0x39354C38,
    0x2C2E3935, 0x26202C2E, 0x28142620, 0x340A2814, 0x4707340A, 0x580A4707,
    0x6613580A, 0x6A206613, 0x6A0D260D, 0x420D4226, 0x4A304226, 0x56354A30,
    0x632F5635, 0x6A1F632F, 0x6A0D6A1F, 0x122B153A, 0x1E1F122B, 0x251F1E1F,
    0x2B2D251F, 0x3C362B2D, 0x49383C36, 0x59354938, 0x662D5935, 0x6A20662D,
    0x64116A20, 0x560A6411, 0x4707560A, 0x360A4707, 0x2A12360A, 0x251F2A12,
    0x6A0D250D, 0x6A206A0D, 0x652C6A20, 0x5A31652C, 0x512F5A31, 0x512F4822,
    0x480D4822, 0x25344822, 0x6A20682F, 0x67136A20, 0x610D6713, 0x5A0B610D,
    0x520E5A0B, 0x4A1A520E, 0x412E4A1A, 0x3D32412E, 0x35343D32, 0x2D303534,
    0x26222D30, 0x280A2622, 0x69396908, 0x261F691F, 0x340B6A0B, 0x6A353435,
    0x2A10340B, 0x251F2A10, 0x292B251F, 0x3435292B, 0x251F6A06, 0x6A3A251F,
    0x260E6A06, 0x511F260E, 0x2634511F, 0x6A382634, 0x6A352508, 0x6A0A2536,
    0x40206A06, 0x6A394020, 0x25204020, 0x6936690B, 0x26096936, 0x26362609,
    0x7117712C, 0x0F177117, 0x0F2C0F17, 0x1933700E, 0x71287112, 0x0F287128,
    0x0F130F28, 0x6A1F4B0C, 0x4B336A1F, 0x0F3C0F03, 0x681D7013, 0x560F5920,
    0x4E312531, 0x562C4E31, 0x5920562C, 0x3F153F31, 0x370D3F15, 0x2F0B370D,
    0x26112F0B, 0x251B2611, 0x2825251B, 0x32312825, 0x290D6F0D, 0x251C290D,
    0x2728251C, 0x36332728, 0x43353633, 0x50314335, 0x562C5031, 0x5924562C,
    0x551B5924, 0x4B0D551B, 0x59255632, 0x55185925, 0x4A0F5518, 0x3F0C4A0F,
    0x330F3F0C, 0x2819330F, 0x26252819, 0x29322625, 0x25317031, 0x27213731,
    0x25192721, 0x29112519, 0x310C2911, 0x3E0A310C, 0x4E0E3E0A, 0x55154E0E,
    0x59215515, 0x53315921, 0x4035400B, 0x4B334035, 0x532F4B33, 0x5728532F,
    0x59205728, 0x56155920, 0x4C0D5615, 0x400B4C0D, 0x320D400B, 0x2815320D,
    0x25242815, 0x28332524, 0x661B261B, 0x6E397027, 0x6D20661B, 0x70276D20,
    0x4F074F37, 0x58285837, 0x512E5828, 0x591C5828, 0x5411591C, 0x4A0D5411,
    0x3D124A0D, 0x391E3D12, 0x370E3D12, 0x310C370E, 0x2B0F310C, 0x28142B0F,
    0x282A2814, 0x220C2814, 0x2532282A, 0x21352532, 0x1C362135, 0x16331C36,
    0x122D1633, 0x0E1E122D, 0x110F0E1E, 0x150A110F, 0x1B09150A, 0x220C1B09,
    0x3C2A391E, 0x47303C2A, 0x512E4730, 0x250D700D, 0x25324F32, 0x5519490D,
    0x59235519, 0x562D5923, 0x4F32562D, 0x2634260D, 0x57212621, 0x570E5721,
    0x6A1F6D1F, 0x6A296D29, 0x572B570D, 0x1E2B572B, 0x0E16110A, 0x11220E16,
    0x1E2B1122, 0x250F700F, 0x411B410F, 0x5932411B, 0x2533411B, 0x2633260D,
    0x6F212621, 0x6F0E6F21, 0x25085808, 0x25204C20, 0x25365136, 0x56134808,
    0x59195613, 0x551E5919, 0x4C20551E, 0x572A4C20, 0x5930572A, 0x57345930,
    0x51365734, 0x580D250D, 0x25324E32, 0x54174A0D, 0x59245417, 0x542F5924,
    0x4E32542F, 0x500E3E09, 0x591D500E, 0x552E591D, 0x4834552E, 0x35354834,
    0x292C3535, 0x251F292C, 0x2A11251F, 0x330B2A11, 0x3E09330B, 0x570D0E0D,
    0x571C490D, 0x5925571C, 0x54305925, 0x43355430, 0x31324335, 0x28283132,
    0x251D2828, 0x290D251D, 0x0F315531, 0x591F5531, 0x5312591F, 0x400A5312,
    0x2E0D400A, 0x27132E0D, 0x251B2713, 0x2B26251B, 0x37312B26, 0x570F250F,
    0x571F480F, 0x5928571F, 0x55325928, 0x49355532, 0x5921572F, 0x56145921,
    0x4D0F5614, 0x44134D0F, 0x3B2B4413, 0x36303B2B, 0x30323630, 0x292C3032,
    0x251D292C, 0x280D251D, 0x69173317, 0x2A1B3317, 0x26272A1B, 0x27342627,
    0x58345806, 0x2F0D580D, 0x26325932, 0x28132F0D, 0x251B2813, 0x2A26251B,
    0x34322A26, 0x251F5809, 0x5835251F, 0x25115806, 0x4A1F2511, 0x24314A1F,
    0x58382431, 0x5833250B, 0x580D2534, 0x171A5935, 0x1113171A, 0x10071113,
    0x241F580A, 0x5831580D, 0x260C5831, 0x2634260C, 0x6F24712F, 0x671D6F24,
    0x4E1D671D, 0x48194E1D, 0x430C4819, 0x3F19430C, 0x371E3F19, 0x1B1E371E,
    0x13221B1E, 0x0F2F1322, 0x0F1F7C1F, 0x6F1A710F, 0x67216F1A, 0x4E216721,
    0x48254E21, 0x43324825, 0x3F254332, 0x37203F25, 0x1B203720, 0x131C1B20,
    0x0F0F131C, 0x450A3D08, 0x4A13450A, 0x431E4A13, 0x3B27431E, 0x392D3B27,
    0x3F35392D, 0x47373F35
};

It contains line segments for all the 95 printable ASCII characters (not including the space character), so starting with ! and ending with ~. Line segment are encoded by taking the four integer coordinates (start_x, start_y, end_x, end_y) with values ranging from 0-128, and packing them into a single 32-bit integer. The aspect ratio of characters is 1:2.

Here is how you might use this table to render text in raylib (for example):

void DrawDebugText3D(
    const char* string, 
    Vector3 location, 
    Quaternion rotation, 
    Color color,
    float thick,      // Thickness. Set to 0.0f for pixel lines.
    float scale,      // Overall scale. Default to 1.0f
    float width,      // Width. Default to 1.0f
    float height,     // Height. Default to 1.0f
    float spacing,    // Character Spacing. Default to 0.0f
    float lineheight) // Line Spacing. Default to 1.0f
{
    int slen = strlen(string);
  
    float xOffset = 0.0;
    float yOffset = 0.0;
  
    for (int i = 0; i < slen; i++)
    {
        if (string[i] == '\n')
        {
            // If we have a line break then reset xOffset and move to next line
            xOffset = 0.0;
            yOffset -= lineheight * scale * height;
        }
        else if (string[i] == '\r')
        {
            // Carriage return resets the xOffset
            xOffset = 0.0;
        }
        else if (string[i] == '\t')
        {
            // Tab is like four spaces
            xOffset += 4.0f * 0.5f * scale * width + spacing;          
        }
        else if (string[i] < '!' || string[i] > '~')
        {
            // Every other non-printable character is rendered as a space
            xOffset += 0.5f * scale * width + spacing;
        }
        else
        {
            // Get the look-up table index from the character code
            int ci = string[i] - '!';
            
            // Get the number of line segments and the offset into the lines array
            int lnum = consolines_nums[ci];
            int loff = consolines_offsets[ci];
            
            for (int li = 0; li < lnum; li++)
            {
                // Unpack integer coordinates
                int xStartInt = (0x000000FF & (consolines_lines[loff + li] >> 0));
                int yStartInt = (0x000000FF & (consolines_lines[loff + li] >> 8));
                int xEndInt = (0x000000FF & (consolines_lines[loff + li] >> 16));
                int yEndInt = (0x000000FF & (consolines_lines[loff + li] >> 24));
              
                // Compute the line start position in 3D space
                Vector3 start = (Vector3){ 
                    xOffset + scale *  width * (       ((float)xStartInt) / 128),
                    yOffset - scale * height * (1.0f - ((float)yStartInt) / 128),
                    0.0f };
              
                start = Vector3Add(
                    Vector3RotateByQuaternion(start, rotation), location);
                
                // Compute the line end position in 3D space
                Vector3 end = (Vector3){ 
                    xOffset + scale *  width * (       ((float)xEndInt) / 128),
                    yOffset - scale * height * (1.0f - ((float)yEndInt) / 128),
                    0.0f };
                
                end = Vector3Add(
                    Vector3RotateByQuaternion(end, rotation), location);
                
                // If thickness is zero draw as a line otherwise draw as a capsule
                if (thick == 0.0f)
                {
                    DrawLine3D(start, end, color);
                }
                else
                {
                    DrawCapsule(start, end, thick, 5, 2, color);                  
                }
            }
            
            // Shift forward to next location
            xOffset += 0.5f * scale * width + spacing;
        }
    }
}

And here is what it looks like in action:

As you can see, for non-zero-thickness lines it draws capsules, which produces a really nice effect.

You can find the PSD file, SVG file, and the quick and dirty Python script I used for making the look-up table here.