Alpha release of "Font Pal", a new Arduino font manager for HD44780 LCDs

Here is the latest version of LCDfontManager:

/*-------------------------------------------------------*/
/* LCDfontManager() - Liquid Crstal Display Font Manager */
/*-------------------------------------------------------*/
void LCDfontManager() {
  //Serial.begin(9600);
  bool fontEncoded = true; 
  String merged_lcdString= "";
  char CC_char_id[3] = {"  "};

  // BEGIN FONT PROCESSING...
  // ==[1]==================== 
  // SCAN lcdString TO DETECT PRESENCE OF '|' FONT-ENCODING MARKERS... 
  int8_t marker_idx = lcdString.indexOf('|');
  if (marker_idx == -1) fontEncoded = false; // If lcdString is plain text, print it more-or-less directly...
  
  // ==[2]==================== 
  // PERFORM AN INTERLEAVE MERGE OF fontChar & fontFlag SECTIONS OF lcdString, EXCLUDING THE '|' MARKER.
  // (e.g.,  "LR DoobyoD| ll bBB LBb" --> "LlRl  DboBoBb yLoBDb"). This also processes the top row of
  // double-row symbols (e.g., "Test|b   CASE|bbbb|" --> "Tbe s t "):
  // %%%%% The setupRow2() function allows the second row of a double-row
  //       symbol to be processed by LCDfontManager as a pseudo-first row.
  //       The font-encoding test cases give several examples of this.
  if (fontEncoded == true) {
    merged_lcdString = "";
    for (uint8_t i = 0; i < marker_idx; i++) {
      merged_lcdString += lcdString.charAt(i);
      merged_lcdString += lcdString.charAt(marker_idx + 1 + i);
    } 
  } 
  else { // if lcdString isn't font-encoded, treat as plain text...
    merged_lcdString = "";
    for (uint8_t i = 0; i < lcdString.length(); i++) merged_lcdString += (lcdString.charAt(i) + String(" "));
    in_CC_id_table = false;
  }

  // ==[3]==================== 
  // TO MAXIMIZE THE EIGHT-CC CGRAM RESOURCE, WE NOW NEED TO CLEAN UP THE CC_id_table, REMOVING ANY TABLE
  // ENTRIES THAT WILL BE OVERWRITTEN BY CCs IN THE NEW lcdString, BUT DON'T EXIST ELSEWHERE IN screenImage... 
  int16_t mystartPos; 
  mystartPos = startPos << 1;
  uint8_t scrn_ptr = mystartPos;
  while (scrn_ptr < (mystartPos + merged_lcdString.length())) {
    uint8_t fontByte = screenImage.charAt(scrn_ptr);
    // Overwrite the character before checking if it exists elsewhere in screenImage:
    screenImage.setCharAt(scrn_ptr, ' '); // (david_2018 patch 9/26/2024) 
    if ((fontByte >= 0x08) && (fontByte <= 0x0f) && (screenImage.indexOf(fontByte) == -1)) CC_id_table[fontByte - 8] = ""; 
    scrn_ptr += 2; 
  }
  screenImage = screenImage.substring(0, mystartPos) + merged_lcdString + screenImage.substring((mystartPos) + merged_lcdString.length());
  
  // ==[4]==================== 
  // BEGIN A LONG LOOP, PROCESSING THE MERGED LCDSTRING INTO SCREENIMAGE AT STARTPOS, LOADING CC 
  // BITMAPS INTO THE CGRAM, FLAGGING FONT ERRORS AND ANY CGRAM OVERFLOW (@ and # errors, respectively).
  // Parse screenImage for CC flags, loading the CC bitmaps to the LCD chip, also replacing fontChars
  // in screenImage with CGRAM_ptrs. Processed CCs have the fontChar part replaced with a byte between 0x08
  // and 0x0f, and the fontFlag part with " ". Subtracting 8 from the 0x08-0x0f byte gives the 0-7 location
  // of the CC in the CC_id_table. This offset of 8 avoids problems caused by ASCII null characters (0x00),
  // related to the use of \0 as an end-of-string marker in C and C++.
   
  scrn_ptr = startPos; // Lines 60-71 rewritten. Also see comment on line 79.
  while (scrn_ptr < 40) {
    uint16_t even_scrn_ptr = scrn_ptr << 1;
    String current_CC_id = screenImage.substring(even_scrn_ptr,even_scrn_ptr+2);
    current_CC_id.toCharArray(CC_char_id,3);
    const char fontChar = CC_char_id[0];
    const char fontFlag = CC_char_id[1];

    // %%%%% If the current fontChar has a non-blank fontFlag, we assume that it must be an unprocessed CC...
    //       (There are two types of CCs: Those that are in the cc_id_table and those that are not.)
    if (CC_char_id[1] != ' ') {
      uint8_t* gBitmap = getBitmap(fontChar, fontFlag); //revised
      if (gBitmap) {
        // Type (A) --- Those that already have a bitmap in CGRAM and are in the CC_id_table: 
        // For these we replace all instances of it in screenImage with the index of the current_CC_id in the CC_id_table (0x08-0x0f + " ")...
        in_CC_id_table = false; // initial assumption is that current_CC_id is not in CC_id_table
        for (CGRAM_ptr = 0; CGRAM_ptr < 8; CGRAM_ptr++)
        if (CC_id_table[CGRAM_ptr] == current_CC_id) {  // E.g., if CC_id_table[0-7] == "ql", then...
          in_CC_id_table = true; // ...set in_CC_id_table to true, and replace all instances of current_CC_id: 
          screenImage.replace(current_CC_id, String(char(CGRAM_ptr + 0x08)) + String(" ")); // We need to rewrite replace to use only even byte boundaries!
        } 
             
        // Type (B) --- Those that have no bitmap and are not in the CC_id_table: 
        // IF the current_CC_id is not already in the CC_id_table, save it to a free location in the table, and then replace
        // all instances of it in screenImage with the index of the current_CC_id in the CC_id_table (0x08-0x0f + " " )...   
        if (in_CC_id_table == false) { 
          // Locate 1st free location in CC_id_table:  
          CGRAM_ptr = 0; while ((CGRAM_ptr < 8) && (CC_id_table[CGRAM_ptr] != "")) CGRAM_ptr++; 
          
          // If CC_id_table has a free location, save the current_CC_id there,
          // load the bitmap into CGRAM, and replace all instances in screenImage with (CGRAM_ptr + 0x08) + " ":
          if (CGRAM_ptr < 8) {
            CC_id_table[CGRAM_ptr] = current_CC_id;   // CC_id format is, e.g., "ql"
            lcd.createChar((CGRAM_ptr + 0x08), gBitmap); // Load bitmap into the LCD chip's CGRAM
            in_CC_id_table = true; // Set in == true, and then replace all instances of current_CC_id in...
            screenImage.replace(current_CC_id, String(char(CGRAM_ptr + 0x08)) + String(" ")); // ...screenImage with (CGRAM_ptr + 0x08) + " " 
          }

          // If the CC_id_table is full (due to all eight CCGRAM locations being in use),
          // signal CGRAM overflow by replacing all instances of the current_CC_id in screenImage with "# ": 
          if (CGRAM_ptr == 8) screenImage.replace(current_CC_id, String("# ")); 
        }  
      } 

       // IF the current bitmap is not found (due to the char/font combination being missing or incorrect), 
       // signal a Char/Font Error by replacing all instances of the current_CC_id in screenImage with "@ ":  
      else screenImage.replace(current_CC_id, String("@ "));
    }
    // %%%%% CC_ids ignored by the above font processing include the following:
    //       Plain-text characters in font-formatted Strings, e.g., "a ";
    //       Characters in plain-text Strings e.g., "Plain Text!";
    //       Processed CCs, e.g. String(char(0x0a) + String(" "); 
    //       The "@ " (bad char/font), and "# " (CGRAM overflow) error flags.
    // 
    scrn_ptr += 1; // Loop back to beginning to process the next CC_id in screenImage...   
  } 

  // ==[5]==================== 
  // FINALLY, PRINT ALL EVEN CHARACTERS IN screenImage 0,2,4,6,..., TO THE LCD:
  lcd.setCursor(0,0);
  String final_screenImage = "";
  for (scrn_ptr = 0; scrn_ptr < screenImage.length(); scrn_ptr += 2) final_screenImage = final_screenImage + screenImage.charAt(scrn_ptr);
  lcd.setCursor(0,0); // row 0
  lcd.print(final_screenImage.substring(0, 20));
  lcd.setCursor(0,1); // row 1
  lcd.print(final_screenImage.substring(20, 40));
  final_screenImage = "";
}