Bit field definition like Borland C++ or codevision

Each reference you create takes more memory overhead.

I try to keep those references as comments, as the example below.
This is a read-only situation, but a write could easily be done with an or.

Just my opinion but this is way to much complication for such a tiny device.

// Update a single digit to the display buffer
void digit_on(uint8_t digit_pixels[], uint8_t x_offset) {
  // 4 sets, 2 rows per, 8 rows total
  for (index = 0; index < 4; index++) {
    
    // Contains 2 rows, one per nibble
    // Upper row in MS nibble
    // Lower row in LS nibble
    // 0bUUUULLLL
    rows = digit_pixels[index]; 

    // Upper Row
    x = x_offset;       // adjust digit position on screen                  
    y = index + index;  //   2k, even row
    //         color shift        Modulo 8
    color_y = (y + color_shift) & 0b00000111;
    
    if (rows & 128)   leds[pos[y][x]] = font_color[color_y];  // check b2^7
    x++;
    if (rows & 64)    leds[pos[y][x]] = font_color[color_y];  // check b2^6 
    x++;
    if (rows & 32)    leds[pos[y][x]] = font_color[color_y];  // check b2^5 
    x++;
    if (rows & 16)    leds[pos[y][x]] = font_color[color_y];  // check b2^4

    // Lower Row
    x = x_offset; // reset x
    // 2k+1   Modulo 8
    y++;
    color_y = (y + color_shift) & 0b00000111;
    
    if (rows & 8)     leds[pos[y][x]] = font_color[color_y];   // check b2^3
    x++;
    if (rows & 4)     leds[pos[y][x]] = font_color[color_y];   // check b2^2
    x++;
    if (rows & 2)     leds[pos[y][x]] = font_color[color_y];   // check b2^1
    x++;
    if (rows & 1)     leds[pos[y][x]] = font_color[color_y];   // check b2^0
  }
}