text: /***************************************************
    Arduino TFT graphics library targeted at ESP8266
    and ESP32 based boards.
  
    This is a standalone library that contains the
    hardware driver, the graphics functions and the
    proportional fonts.
  
    The larger fonts are Run Length Encoded to reduce their
    size.
  
    Created by Bodmer 2/12/16
    Bodmer: Added RPi 16 bit display support
   ****************************************************/
  
  
  #include "In_eSPI.h"
  
  #if defined (ESP32)
    #if !defined (ESP32_PARALLEL)
      #ifdef USE_HSPI_PORT
        SPIClass spi = SPIClass(HSPI);
      #else // use default VSPI port
        SPIClass& spi = SPI;
      #endif
    #endif
  #else // ESP8266
    SPIClass& spi = SPI;
  #endif
  
    // SUPPORT_TRANSACTIONS is mandatory for ESP32 so the hal mutex is toggled
  #if defined (ESP32) && !defined (SUPPORT_TRANSACTIONS)
    #define SUPPORT_TRANSACTIONS
  #endif
  
  // If it is a 16bit serial display we must transfer 16 bits every time
  #ifdef RPI_ILI9486_DRIVER
    #define CMD_BITS (16-1)
  #else
    #define CMD_BITS (8-1)
  #endif
  
  // Fast block write prototype
  void writeBlock(uint16_t color, uint32_t repeat);
  
  // Byte read prototype
  uint8_t readByte(void);
  
  // GPIO parallel input/output control
  void busDir(uint32_t mask, uint8_t mode);
  
  void gpioMode(uint8_t gpio, uint8_t mode);
  
  inline void TFT_eSPI::spi_begin(void){
  #if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) && !defined(ESP32_PARALLEL)
    if (locked) {locked = false; spi.beginTransaction(SPISettings(SPI_FREQUENCY, MSBFIRST, TFT_SPI_MODE)); CS_L;}
  #else
    CS_L;
  #endif
  #ifdef ESP8266
    SPI1U = SPI1U_WRITE;
  #endif
  }
  
  inline void TFT_eSPI::spi_end(void){
  #if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) && !defined(ESP32_PARALLEL)
    if(!inTransaction) {if (!locked) {locked = true; CS_H; spi.endTransaction();}}
    #ifdef ESP8266
      SPI1U = SPI1U_READ;
    #endif
  #else
    if(!inTransaction) {CS_H;}
  #endif
  }
  
  inline void TFT_eSPI::spi_begin_read(void){
  #if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) && !defined(ESP32_PARALLEL)
    if (locked) {locked = false; spi.beginTransaction(SPISettings(SPI_READ_FREQUENCY, MSBFIRST, TFT_SPI_MODE)); CS_L;}
  #else
    #if !defined(ESP32_PARALLEL)
      spi.setFrequency(SPI_READ_FREQUENCY);
    #endif
     CS_L;
  #endif
  #ifdef ESP8266
    SPI1U = SPI1U_READ;
  #endif
  }
  
  inline void TFT_eSPI::spi_end_read(void){
  #if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) && !defined(ESP32_PARALLEL)
    if(!inTransaction) {if (!locked) {locked = true; CS_H; spi.endTransaction();}}
  #else
    #if !defined(ESP32_PARALLEL)
      spi.setFrequency(SPI_FREQUENCY);
    #endif
     if(!inTransaction) {CS_H;}
  #endif
  #ifdef ESP8266
    SPI1U = SPI1U_WRITE;
  #endif
  }
  
  #if defined (TOUCH_CS) && defined (SPI_TOUCH_FREQUENCY) // && !defined(ESP32_PARALLEL)
  
    inline void TFT_eSPI::spi_begin_touch(void){
     CS_H; // Just in case it has been left low
  
    #if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS)
      if (locked) {locked = false; spi.beginTransaction(SPISettings(SPI_TOUCH_FREQUENCY, MSBFIRST, SPI_MODE0));}
    #else
      spi.setFrequency(SPI_TOUCH_FREQUENCY);
    #endif
  
    #ifdef ESP8266
      SPI1U = SPI1U_READ;
    #endif
  
    T_CS_L;
    }
  
    inline void TFT_eSPI::spi_end_touch(void){
    T_CS_H;
  
    #if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS)
      if(!inTransaction) {if (!locked) {locked = true; spi.endTransaction();}}
    #else
      spi.setFrequency(SPI_FREQUENCY);
    #endif
  
    #ifdef ESP8266
      SPI1U = SPI1U_WRITE;
    #endif
    }
  
  #endif
  
  /***************************************************************************************
  ** Function name:           TFT_eSPI
  ** Description:             Constructor , we must use hardware SPI pins
  ***************************************************************************************/
  TFT_eSPI::TFT_eSPI(int16_t w, int16_t h)
  {
  
  // The control pins are deliberately set to the inactive state (CS high) as setup()
  // might call and initialise other SPI peripherals which would could cause conflicts
  // if CS is floating or undefined.
  #ifdef TFT_CS
    digitalWrite(TFT_CS, HIGH); // Chip select high (inactive)
    pinMode(TFT_CS, OUTPUT);
  #endif
  
  // Configure chip select for touchscreen controller if present
  #ifdef TOUCH_CS
    digitalWrite(TOUCH_CS, HIGH); // Chip select high (inactive)
    pinMode(TOUCH_CS, OUTPUT);
  #endif
  
  #ifdef TFT_WR
    digitalWrite(TFT_WR, HIGH); // Set write strobe high (inactive)
    pinMode(TFT_WR, OUTPUT);
  #endif
  
  #ifdef TFT_DC
    digitalWrite(TFT_DC, HIGH); // Data/Command high = data mode
    pinMode(TFT_DC, OUTPUT);
  #endif
  
  #ifdef TFT_RST
    if (TFT_RST >= 0) {
      digitalWrite(TFT_RST, HIGH); // Set high, do not share pin with another SPI device
      pinMode(TFT_RST, OUTPUT);
    }
  #endif
  
  #ifdef ESP32_PARALLEL
  
    // Create a bit set lookup table for data bus - wastes 1kbyte of RAM but speeds things up dramatically
    for (int32_t c = 0; c<256; c++)
    {
      xset_mask[c] = 0;
      if ( c & 0x01 ) xset_mask[c] |= (1 << TFT_D0);
      if ( c & 0x02 ) xset_mask[c] |= (1 << TFT_D1);
      if ( c & 0x04 ) xset_mask[c] |= (1 << TFT_D2);
      if ( c & 0x08 ) xset_mask[c] |= (1 << TFT_D3);
      if ( c & 0x10 ) xset_mask[c] |= (1 << TFT_D4);
      if ( c & 0x20 ) xset_mask[c] |= (1 << TFT_D5);
      if ( c & 0x40 ) xset_mask[c] |= (1 << TFT_D6);
      if ( c & 0x80 ) xset_mask[c] |= (1 << TFT_D7);
    }
  
    // Make sure read is high before we set the bus to output
    digitalWrite(TFT_RD, HIGH);
    pinMode(TFT_RD, OUTPUT);
    
    GPIO.out_w1ts = set_mask(255); // Set data bus to 0xFF
  
    // Set TFT data bus lines to output
    busDir(dir_mask, OUTPUT);
  
  #endif
  
    _init_width  = _width  = w; // Set by specific xxxxx_Defines.h file or by users sketch
    _init_height = _height = h; // Set by specific xxxxx_Defines.h file or by users sketch
    rotation  = 0;
    cursor_y  = cursor_x  = 0;
    textfont  = 1;
    textsize  = 1;
    textcolor   = bitmap_fg = 0xFFFF; // White
    textbgcolor = bitmap_bg = 0x0000; // Black
    padX = 0;             // No padding
    isDigits   = false;   // No bounding box adjustment
    textwrapX  = true;    // Wrap text at end of line when using print stream
    textwrapY  = false;   // Wrap text at bottom of screen when using print stream
    textdatum = TL_DATUM; // Top Left text alignment is default
    fontsloaded = 0;
  
    _swapBytes = false;   // Do not swap colour bytes by default
  
    locked = true;        // ESP32 transaction mutex lock flags
    inTransaction = false;
  
    _booted   = true;
    _cp437    = true;
    _utf8     = true;
  
    addr_row = 0xFFFF;
    addr_col = 0xFFFF;
  
    _xpivot = 0;
    _ypivot = 0;
  
    cspinmask = 0;
    dcpinmask = 0;
    wrpinmask = 0;
    sclkpinmask = 0;
  
  #ifdef LOAD_GLCD
    fontsloaded  = 0x0002; // Bit 1 set
  #endif
  
  #ifdef LOAD_FONT2
    fontsloaded |= 0x0004; // Bit 2 set
  #endif
  
  #ifdef LOAD_FONT4
    fontsloaded |= 0x0010; // Bit 4 set
  #endif
  
  #ifdef LOAD_FONT6
    fontsloaded |= 0x0040; // Bit 6 set
  #endif
  
  #ifdef LOAD_FONT7
    fontsloaded |= 0x0080; // Bit 7 set
  #endif
  
  #ifdef LOAD_FONT8
    fontsloaded |= 0x0100; // Bit 8 set
  #endif
  
  #ifdef LOAD_FONT8N
    fontsloaded |= 0x0200; // Bit 9 set
  #endif
  
  #ifdef SMOOTH_FONT
    fontsloaded |= 0x8000; // Bit 15 set
  #endif
  }
  
  
  /***************************************************************************************
  ** Function name:           begin
  ** Description:             Included for backwards compatibility
  ***************************************************************************************/
  void TFT_eSPI::begin(uint8_t tc)
  {
   init(tc);
  }
  
  
  /***************************************************************************************
  ** Function name:           init (tc is tab colour for ST7735 displays only)
  ** Description:             Reset, then initialise the TFT display registers
  ***************************************************************************************/
  void TFT_eSPI::init(uint8_t tc)
  {
    if (_booted)
    {
  #if !defined (ESP32)
    #if defined (TFT_CS) && (TFT_CS >= 0)
      cspinmask = (uint32_t) digitalPinToBitMask(TFT_CS);
    #endif
  
    #if defined (TFT_DC) && (TFT_DC >= 0)
      dcpinmask = (uint32_t) digitalPinToBitMask(TFT_DC);
    #endif
    
    #if defined (TFT_WR) && (TFT_WR >= 0)
      wrpinmask = (uint32_t) digitalPinToBitMask(TFT_WR);
    #endif
    
    #if defined (TFT_SCLK) && (TFT_SCLK >= 0)
      sclkpinmask = (uint32_t) digitalPinToBitMask(TFT_SCLK);
    #endif
    
    #ifdef TFT_SPI_OVERLAP
      // Overlap mode SD0=MISO, SD1=MOSI, CLK=SCLK must use D3 as CS
      //    pins(int8_t sck, int8_t miso, int8_t mosi, int8_t ss);
      //spi.pins(        6,          7,           8,          0);
      spi.pins(6, 7, 8, 0);
    #endif
  
    spi.begin(); // This will set HMISO to input
  
  #else
    #if !defined(ESP32_PARALLEL)
      #if defined (TFT_MOSI) && !defined (TFT_SPI_OVERLAP)
        spi.begin(TFT_SCLK, TFT_MISO, TFT_MOSI, -1);
      #else
        spi.begin();
      #endif
    #endif
  #endif
  
      inTransaction = false;
      locked = true;
  
    // SUPPORT_TRANSACTIONS is mandatory for ESP32 so the hal mutex is toggled
    // so the code here is for ESP8266 only
  #if !defined (SUPPORT_TRANSACTIONS) && defined (ESP8266)
      spi.setBitOrder(MSBFIRST);
      spi.setDataMode(TFT_SPI_MODE);
      spi.setFrequency(SPI_FREQUENCY);
  #endif
  
  #if defined(ESP32_PARALLEL)
      digitalWrite(TFT_CS, LOW); // Chip select low permanently
      pinMode(TFT_CS, OUTPUT);
  #else
    #ifdef TFT_CS
      // Set to output once again in case D6 (MISO) is used for CS
      digitalWrite(TFT_CS, HIGH); // Chip select high (inactive)
      pinMode(TFT_CS, OUTPUT);
    #else
      spi.setHwCs(1); // Use hardware SS toggling
    #endif
  #endif
  
    // Set to output once again in case D6 (MISO) is used for DC
  #ifdef TFT_DC
      digitalWrite(TFT_DC, HIGH); // Data/Command high = data mode
      pinMode(TFT_DC, OUTPUT);
  #endif
  
      _booted = false;
      spi_end();
    } // end of: if just _booted
  
    // Toggle RST low to reset
    spi_begin();
  
  #ifdef TFT_RST
  #ifdef M5CORE2
    pinMode(TFT_RST, INPUT_PULLDOWN);
    delay(1);
    bool lcd_version = digitalRead(TFT_RST);
    pinMode(TFT_RST, OUTPUT);
  #endif
    if (TFT_RST >= 0) {
      digitalWrite(TFT_RST, HIGH);
      delay(5);
      digitalWrite(TFT_RST, LOW);
      delay(20);
      digitalWrite(TFT_RST, HIGH);
    }
    else writecommand(TFT_SWRST); // Software reset
  #else
    writecommand(TFT_SWRST); // Software reset
  #endif
  
    spi_end();
  
    delay(150); // Wait for reset to complete
  
    spi_begin();
    
    tc = tc; // Supress warning
  
    // This loads the driver specific initialisation code  <<<<<<<<<<<<<<<<<<<<< ADD NEW DRIVERS TO THE LIST HERE <<<<<<<<<<<<<<<<<<<<<<<
  #if   defined (ILI9341_DRIVER)
      #include "ILI9341_Init.h"
  
  #elif defined (ST7735_DRIVER)
      tabcolor = tc;
      #include "ST7735_Init.h"
  
  #elif defined (ILI9163_DRIVER)
      #include "TFT_Drivers/ILI9163_Init.h"
  
  #elif defined (S6D02A1_DRIVER)
      #include "TFT_Drivers/S6D02A1_Init.h"
       
  #elif defined (RPI_ILI9486_DRIVER)
      #include "TFT_Drivers/ILI9486_Init.h"
  
  #elif defined (ILI9486_DRIVER)
      #include "TFT_Drivers/ILI9486_Init.h"
  
  #elif defined (ILI9481_DRIVER)
      #include "TFT_Drivers/ILI9481_Init.h"
  
  #elif defined (ILI9488_DRIVER)
      #include "TFT_Drivers/ILI9488_Init.h"
  
  #elif defined (HX8357D_DRIVER)
      #include "TFT_Drivers/HX8357D_Init.h"
  
  #elif defined (ST7789_DRIVER)
      #include "TFT_Drivers/ST7789_Init.h"
  
  #elif defined (R61581_DRIVER)
      #include "TFT_Drivers/R61581_Init.h"
  
  #elif defined (RM68140_DRIVER)
  	#include "TFT_Drivers/RM68140_Init.h"
  
  #elif defined (ST7789_2_DRIVER)
      #include "TFT_Drivers/ST7789_2_Init.h"
  
  #endif
    writecommand(TFT_INVON);
  
    spi_end();
  
    setRotation(rotation);
  
  #if defined (TFT_BL) && defined (TFT_BACKLIGHT_ON)
    digitalWrite(TFT_BL, TFT_BACKLIGHT_ON);
    pinMode(TFT_BL, OUTPUT);
  #else
  //  #if defined (TFT_BL) && defined (M5STACK)
  //    // Turn on the back-light LED
  //    digitalWrite(TFT_BL, HIGH);
  //    pinMode(TFT_BL, OUTPUT);
  //  #endif
  #endif
  }
  
  
  /***************************************************************************************
  ** Function name:           setRotation
  ** Description:             rotate the screen orientation m = 0-3 or 4-7 for BMP drawing
  ***************************************************************************************/
  void TFT_eSPI::setRotation(uint8_t m)
  {
  
    spi_begin();
  
      // This loads the driver specific rotation code  <<<<<<<<<<<<<<<<<<<<< ADD NEW DRIVERS TO THE LIST HERE <<<<<<<<<<<<<<<<<<<<<<<
  #if   defined (ILI9341_DRIVER)
      #include "ILI9341_Rotation.h"
  
  #elif defined (ST7735_DRIVER)
      #include "ST7735_Rotation.h"
  
  #elif defined (ILI9163_DRIVER)
      #include "TFT_Drivers/ILI9163_Rotation.h"
  
  #elif defined (S6D02A1_DRIVER)
      #include "TFT_Drivers/S6D02A1_Rotation.h"
  
  #elif defined (RPI_ILI9486_DRIVER)
      #include "TFT_Drivers/ILI9486_Rotation.h"
  
  #elif defined (ILI9486_DRIVER)
      #include "TFT_Drivers/ILI9486_Rotation.h"
  
  #elif defined (ILI9481_DRIVER)
      #include "TFT_Drivers/ILI9481_Rotation.h"
  
  #elif defined (ILI9488_DRIVER)
      #include "TFT_Drivers/ILI9488_Rotation.h"
  
  #elif defined (HX8357D_DRIVER)
      #include "TFT_Drivers/HX8357D_Rotation.h"
  
  #elif defined (ST7789_DRIVER)
      #include "TFT_Drivers/ST7789_Rotation.h"
  
  #elif defined (R61581_DRIVER)
      #include "TFT_Drivers/R61581_Rotation.h"
  
  #elif defined (RM68140_DRIVER)
  	#include "TFT_Drivers/RM68140_Rotation.h"
  
  #elif defined (ST7789_2_DRIVER)
      #include "TFT_Drivers/ST7789_2_Rotation.h"
  
  #endif
  
    delayMicroseconds(10);
  
    spi_end();
  
    addr_row = 0xFFFF;
    addr_col = 0xFFFF;
  }
  
  
  /***************************************************************************************
  ** Function name:           commandList, used for FLASH based lists only (e.g. ST7735)
  ** Description:             Get initialisation commands from FLASH and send to TFT
  ***************************************************************************************/
  void TFT_eSPI::commandList (const uint8_t *addr)
  {
    uint8_t  numCommands;
    uint8_t  numArgs;
    uint8_t  ms;
  
    numCommands = pgm_read_byte(addr++);   // Number of commands to follow
  
    while (numCommands--)                  // For each command...
    {
      writecommand(pgm_read_byte(addr++)); // Read, issue command
      numArgs = pgm_read_byte(addr++);     // Number of args to follow
      ms = numArgs & TFT_INIT_DELAY;       // If hibit set, delay follows args
      numArgs &= ~TFT_INIT_DELAY;          // Mask out delay bit
  
      while (numArgs--)                    // For each argument...
      {
        writedata(pgm_read_byte(addr++));  // Read, issue argument
      }
  
      if (ms)
      {
        ms = pgm_read_byte(addr++);        // Read post-command delay time (ms)
        delay( (ms==255 ? 500 : ms) );
      }
    }
  
  }
  
  
  /***************************************************************************************
  ** Function name:           spiwrite
  ** Description:             Write 8 bits to SPI port (legacy support only)
  ***************************************************************************************/
  void TFT_eSPI::spiwrite(uint8_t c)
  {
    tft_Write_8(c);
  }
  
  
  /***************************************************************************************
  ** Function name:           writecommand
  ** Description:             Send an 8 bit command to the TFT
  ***************************************************************************************/
  void TFT_eSPI::writecommand(uint8_t c)
  {
    spi_begin(); // CS_L;
  
    DC_C;
  
    tft_Write_8(c);
  
    DC_D;
  
    spi_end();  // CS_H;
  
  }
  
  
  /***************************************************************************************
  ** Function name:           writedata
  ** Description:             Send a 8 bit data value to the TFT
  ***************************************************************************************/
  void TFT_eSPI::writedata(uint8_t d)
  {
    spi_begin(); // CS_L;
  
    DC_D;        // Play safe, but should already be in data mode
  
    tft_Write_8(d);
  
    CS_L;        // Allow more hold time for low VDI rail
  
    spi_end();   // CS_H;
  }
  
  
  /***************************************************************************************
  ** Function name:           readcommand8
  ** Description:             Read a 8 bit data value from an indexed command register
  ***************************************************************************************/
  uint8_t TFT_eSPI::readcommand8(uint8_t cmd_function, uint8_t index)
  {
    uint8_t reg = 0;
  #ifdef ESP32_PARALLEL
  
    writecommand(cmd_function); // Sets DC and CS high 
  
    busDir(dir_mask, INPUT);
  
    CS_L;
  
    // Read nth parameter (assumes caller discards 1st parameter or points index to 2nd)
    while(index--) reg = readByte();
  
    busDir(dir_mask, OUTPUT);
  
    CS_H;
  
  #else
    // for ILI9341 Interface II i.e. IM [3:0] = "1101"
    spi_begin_read();
    index = 0x10 + (index & 0x0F);
  
    DC_C;
    tft_Write_8(0xD9);
    DC_D;
    tft_Write_8(index);
  
    CS_H; // Some displays seem to need CS to be pulsed here, or is just a delay needed?
    CS_L;
  
    DC_C;
    tft_Write_8(cmd_function);
    DC_D;
    reg = tft_Read_8();
  
    spi_end_read();
  #endif
    return reg;
  }
  
  
  /***************************************************************************************
  ** Function name:           readcommand16
  ** Description:             Read a 16 bit data value from an indexed command register
  ***************************************************************************************/
  uint16_t TFT_eSPI::readcommand16(uint8_t cmd_function, uint8_t index)
  {
    uint32_t reg;
  
    reg  = (readcommand8(cmd_function, index + 0) <<  8);
    reg |= (readcommand8(cmd_function, index + 1) <<  0);
  
    return reg;
  }
  
  
  /***************************************************************************************
  ** Function name:           readcommand32
  ** Description:             Read a 32 bit data value from an indexed command register
  ***************************************************************************************/
  uint32_t TFT_eSPI::readcommand32(uint8_t cmd_function, uint8_t index)
  {
    uint32_t reg;
  
    reg  = (readcommand8(cmd_function, index + 0) << 24);
    reg |= (readcommand8(cmd_function, index + 1) << 16);
    reg |= (readcommand8(cmd_function, index + 2) <<  8);
    reg |= (readcommand8(cmd_function, index + 3) <<  0);
  
    return reg;
  }
  
  
  /***************************************************************************************
  ** Function name:           read pixel (for SPI Interface II i.e. IM [3:0] = "1101")
  ** Description:             Read 565 pixel colours from a pixel
  ***************************************************************************************/
  uint16_t TFT_eSPI::readPixel(int32_t x0, int32_t y0)
  {
  #if defined(ESP32_PARALLEL)
  
    readAddrWindow(x0, y0, 1, 1); // Sets CS low
  
    // Set masked pins D0- D7 to input
    busDir(dir_mask, INPUT); 
  
    // Dummy read to throw away don't care value
    readByte();
  
    // Fetch the 16 bit BRG pixel
    //uint16_t rgb = (readByte() << 8) | readByte();
  
    #if defined (ILI9341_DRIVER) | defined (ILI9488_DRIVER) // Read 3 bytes
  
    // Read window pixel 24 bit RGB values and fill in LS bits
    uint16_t rgb = ((readByte() & 0xF8) << 8) | ((readByte() & 0xFC) << 3) | (readByte() >> 3);
  
    CS_H;
  
    // Set masked pins D0- D7 to output
    busDir(dir_mask, OUTPUT);
  
    return rgb;
  
    #else // ILI9481 16 bit read
  
    // Fetch the 16 bit BRG pixel
    uint16_t bgr = (readByte() << 8) | readByte();
  
    CS_H;
  
    // Set masked pins D0- D7 to output
    busDir(dir_mask, OUTPUT);
  
    // Swap Red and Blue (could check MADCTL setting to see if this is needed)
    return  (bgr>>11) | (bgr<<11) | (bgr & 0x7E0);
    #endif
  
  #else // Not ESP32_PARALLEL
  
    // This function can get called during antialiased font rendering
    // so a transaction may be in progress
    bool wasInTransaction = inTransaction;
    if (inTransaction) { inTransaction= false; spi_end();}
  
    spi_begin_read();
  
    readAddrWindow(x0, y0, 1, 1); // Sets CS low
  
    #ifdef TFT_SDA_READ
      begin_SDA_Read();
    #endif
  
    // Dummy read to throw away don't care value
    tft_Read_8();
    
    //#if !defined (ILI9488_DRIVER)
  
      // Read the 3 RGB bytes, colour is actually only in the top 6 bits of each byte
      // as the TFT stores colours as 18 bits
      uint8_t r = tft_Read_8();
      uint8_t g = tft_Read_8();
      uint8_t b = tft_Read_8();
  /*
    #else
  
      // The 6 colour bits are in MS 6 bits of each byte, but the ILI9488 needs an extra clock pulse
      // so bits appear shifted right 1 bit, so mask the middle 6 bits then shift 1 place left
      uint8_t r = (tft_Read_8()&0x7E)<<1;
      uint8_t g = (tft_Read_8()&0x7E)<<1;
      uint8_t b = (tft_Read_8()&0x7E)<<1;
  
    #endif
  */
    CS_H;
  
    #ifdef TFT_SDA_READ
      end_SDA_Read();
    #endif
  
    spi_end_read();
  
    // Reinstate the transaction if one was in progress
    if(wasInTransaction) { spi_begin(); inTransaction = true; }
  
    return color565(r, g, b);
  
  #endif
  }
  
  void TFT_eSPI::setCallback(getColorCallback getCol)
  {
    getColor = getCol;
  }
  
  /***************************************************************************************
  ** Function name:           read byte  - supports class functions
  ** Description:             Read a byte from ESP32 8 bit data port
  ***************************************************************************************/
  // Bus MUST be set to input before calling this function!
  uint8_t readByte(void)
  {
    uint8_t b = 0;
  
  #ifdef ESP32_PARALLEL
    RD_L;
    uint32_t reg;           // Read all GPIO pins 0-31
    reg = gpio_input_get(); // Read three times to allow for bus access time
    reg = gpio_input_get();
    reg = gpio_input_get(); // Data should be stable now
    RD_H;
  
    // Check GPIO bits used and build value
    b  = (((reg>>TFT_D0)&1) << 0);
    b |= (((reg>>TFT_D1)&1) << 1);
    b |= (((reg>>TFT_D2)&1) << 2);
    b |= (((reg>>TFT_D3)&1) << 3);
    b |= (((reg>>TFT_D4)&1) << 4);
    b |= (((reg>>TFT_D5)&1) << 5);
    b |= (((reg>>TFT_D6)&1) << 6);
    b |= (((reg>>TFT_D7)&1) << 7);
  #endif
  
    return b;
  }
  
  /***************************************************************************************
  ** Function name:           GPIO direction control  - supports class functions
  ** Description:             Set parallel bus to input or output
  ***************************************************************************************/
  #ifdef ESP32_PARALLEL
  void busDir(uint32_t mask, uint8_t mode)
  {//*
    gpioMode(TFT_D0, mode);
    gpioMode(TFT_D1, mode);
    gpioMode(TFT_D2, mode);
    gpioMode(TFT_D3, mode);
    gpioMode(TFT_D4, mode);
    gpioMode(TFT_D5, mode);
    gpioMode(TFT_D6, mode);
    gpioMode(TFT_D7, mode);
    return; //*/
  
    /*
    // Arduino generic native function, but slower
    pinMode(TFT_D0, mode);
    pinMode(TFT_D1, mode);
    pinMode(TFT_D2, mode);
    pinMode(TFT_D3, mode);
    pinMode(TFT_D4, mode);
    pinMode(TFT_D5, mode);
    pinMode(TFT_D6, mode);
    pinMode(TFT_D7, mode);
    return; //*/
  }
  
  // Set ESP32 GPIO pin to input or output
  void gpioMode(uint8_t gpio, uint8_t mode)
  {
    if(mode == INPUT) GPIO.enable_w1tc = ((uint32_t)1 << gpio);
    else GPIO.enable_w1ts = ((uint32_t)1 << gpio);
    ESP_REG(DR_REG_IO_MUX_BASE + esp32_gpioMux[gpio].reg) = ((uint32_t)2 << FUN_DRV_S) | (FUN_IE) | ((uint32_t)2 << MCU_SEL_S);
    GPIO.pin[gpio].val = 0;
  }
  #endif
  
  /***************************************************************************************
  ** Function name:           read rectangle (for SPI Interface II i.e. IM [3:0] = "1101")
  ** Description:             Read 565 pixel colours from a defined area
  ***************************************************************************************/
  void TFT_eSPI::readRect(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *data)
  {
    if ((x > _width) || (y > _height) || (w == 0) || (h == 0)) return;
  
  #if defined(ESP32_PARALLEL)
  
    readAddrWindow(x, y, w, h); // Sets CS low
  
    // Set masked pins D0- D7 to input
    busDir(dir_mask, INPUT);
  
    // Dummy read to throw away don't care value
    readByte();
  
    // Total pixel count
    uint32_t len = w * h;
  
  #if defined (ILI9341_DRIVER) | defined (ILI9488_DRIVER) // Read 3 bytes
    // Fetch the 24 bit RGB value
    while (len--) {
      // Assemble the RGB 16 bit colour
      uint16_t rgb = ((readByte() & 0xF8) << 8) | ((readByte() & 0xFC) << 3) | (readByte() >> 3);
  
      // Swapped byte order for compatibility with pushRect()
      *data++ = (rgb<<8) | (rgb>>8);
    }
  #else // ILI9481 reads as 16 bits
    // Fetch the 16 bit BRG pixels
    while (len--) {
      // Read the BRG 16 bit colour
      uint16_t bgr = (readByte() << 8) | readByte();
  
      // Swap Red and Blue (could check MADCTL setting to see if this is needed)
      uint16_t rgb = (bgr>>11) | (bgr<<11) | (bgr & 0x7E0);
  
      // Swapped byte order for compatibility with pushRect()
      *data++ = (rgb<<8) | (rgb>>8);
    }
  #endif
    CS_H;
  
    // Set masked pins D0- D7 to output
    busDir(dir_mask, OUTPUT);
  
  #else // Not ESP32_PARALLEL
  
    spi_begin_read();
  
    readAddrWindow(x, y, w, h); // Sets CS low
  
    #ifdef TFT_SDA_READ
      begin_SDA_Read();
    #endif
  
    // Dummy read to throw away don't care value
    tft_Read_8();
  
    // Read window pixel 24 bit RGB values
    uint32_t len = w * h;
    while (len--) {
  
    #if !defined (ILI9488_DRIVER)
  
      // Read the 3 RGB bytes, colour is actually only in the top 6 bits of each byte
      // as the TFT stores colours as 18 bits
      uint8_t r = tft_Read_8();
      uint8_t g = tft_Read_8();
      uint8_t b = tft_Read_8();
  
    #else
  
      // The 6 colour bits are in LS 6 bits of each byte but we do not include the extra clock pulse
      // so we use a trick and mask the middle 6 bits of the byte, then only shift 1 place left
      uint8_t r = (tft_Read_8()&0x7E)<<1;
      uint8_t g = (tft_Read_8()&0x7E)<<1;
      uint8_t b = (tft_Read_8()&0x7E)<<1;
  
    #endif
  
      // Swapped colour byte order for compatibility with pushRect()
      *data++ = (r & 0xF8) | (g & 0xE0) >> 5 | (b & 0xF8) << 5 | (g & 0x1C) << 11;
    }
  
    CS_H;
  
    #ifdef TFT_SDA_READ
      end_SDA_Read();
    #endif
  
    spi_end_read();
  
  #endif
  }
  
  /***************************************************************************************
  ** Function name:           tft_Read_8
  ** Description:             Software SPI to read bidirectional SDA line
  ***************************************************************************************/
  #if defined (ESP8266) && defined (TFT_SDA_READ)
  uint8_t TFT_eSPI::tft_Read_8(void)
  {
    uint8_t  ret = 0;
    uint32_t reg = 0;
  
    for (uint8_t i = 0; i < 8; i++) {  // read results
      ret <<= 1;
      SCLK_L;
      if (digitalRead(TFT_MOSI)) ret |= 1;
      SCLK_H;
    }
  
    return ret;
  }
  #endif
  
  /***************************************************************************************
  ** Function name:           beginSDA
  ** Description:             Detach SPI from pin to permit software SPI
  ***************************************************************************************/
  #ifdef TFT_SDA_READ
  void TFT_eSPI::begin_SDA_Read(void)
  {
    #ifdef ESP32
      pinMatrixOutDetach(TFT_MOSI, false, false);
      pinMode(TFT_MOSI, INPUT);
      pinMatrixInAttach(TFT_MOSI, VSPIQ_IN_IDX, false);
    #else // ESP8266
      #ifdef TFT_SPI_OVERLAP
        // Reads in overlap mode not supported
      #else
        spi.end();
      #endif
    #endif
  }
  #endif
  
  /***************************************************************************************
  ** Function name:           endSDA
  ** Description:             Attach SPI pins after software SPI
  ***************************************************************************************/
  #ifdef TFT_SDA_READ
  void TFT_eSPI::end_SDA_Read(void)
  {
    #ifdef ESP32
      pinMode(TFT_MOSI, OUTPUT);
      pinMatrixOutAttach(TFT_MOSI, VSPID_OUT_IDX, false, false);
      pinMode(TFT_MISO, INPUT);
      pinMatrixInAttach(TFT_MISO, VSPIQ_IN_IDX, false);
    #else
      #ifdef TFT_SPI_OVERLAP
        spi.pins(6, 7, 8, 0);
      #else
        spi.begin();
      #endif
    #endif
  }
  #endif
  
  /***************************************************************************************
  ** Function name:           push rectangle (for SPI Interface II i.e. IM [3:0] = "1101")
  ** Description:             push 565 pixel colours into a defined area
  ***************************************************************************************/
  void TFT_eSPI::pushRect(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *data)
  {
    // Function deprecated, remains for backwards compatibility
    // pushImage() is better as it will crop partly off-screen image blocks
    pushImage(x, y, w, h, data);
  }
  
  
  /***************************************************************************************
  ** Function name:           pushImage
  ** Description:             plot 16 bit colour sprite or image onto TFT
  ***************************************************************************************/
  void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *data)
  {
  
    if ((x >= _width) || (y >= _height)) return;
  
    int32_t dx = 0;
    int32_t dy = 0;
    int32_t dw = w;
    int32_t dh = h;
  
    if (x < 0) { dw += x; dx = -x; x = 0; }
    if (y < 0) { dh += y; dy = -y; y = 0; }
  
    if ((x + dw) > _width ) dw = _width  - x;
    if ((y + dh) > _height) dh = _height - y;
  
    if (dw < 1 || dh < 1) return;
  
    spi_begin();
    inTransaction = true;
  
    setWindow(x, y, x + dw - 1, y + dh - 1);
  
    data += dx + dy * w;
  
    while (dh--)
    {
      pushColors(data, dw, _swapBytes);
      data += w;
    }
  
    inTransaction = false;
    spi_end();
  }
  
  /***************************************************************************************
  ** Function name:           pushImage
  ** Description:             plot 16 bit sprite or image with 1 colour being transparent
  ***************************************************************************************/
  void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *data, uint16_t transp)
  {
  
    if ((x >= _width) || (y >= _height)) return;
  
    int32_t dx = 0;
    int32_t dy = 0;
    int32_t dw = w;
    int32_t dh = h;
  
    if (x < 0) { dw += x; dx = -x; x = 0; }
    if (y < 0) { dh += y; dy = -y; y = 0; }
    
    if ((x + dw) > _width ) dw = _width  - x;
    if ((y + dh) > _height) dh = _height - y;
  
    if (dw < 1 || dh < 1) return;
  
    spi_begin();
    inTransaction = true;
  
    data += dx + dy * w;
  
    int32_t xe = x + dw - 1, ye = y + dh - 1;
  
    uint16_t  lineBuf[dw];
  
    if (!_swapBytes) transp = transp >> 8 | transp << 8;
  
    while (dh--)
    {
      int32_t len = dw;
      uint16_t* ptr = data;
      int32_t px = x;
      boolean move = true;
      uint16_t np = 0;
  
      while (len--)
      {
        if (transp != *ptr)
        {
          if (move) { move = false; setWindow(px, y, xe, ye); }
          lineBuf[np] = *ptr;
          np++;
        }
        else
        {
          move = true;
          if (np)
          {
             pushColors((uint16_t*)lineBuf, np, _swapBytes);
             np = 0;
          }
        }
        px++;
        ptr++;
      }
      if (np) pushColors((uint16_t*)lineBuf, np, _swapBytes);
  
      y++;
      data += w;
    }
  
    inTransaction = false;
    spi_end();
  }
  
  
  /***************************************************************************************
  ** Function name:           pushImage - for FLASH (PROGMEM) stored images
  ** Description:             plot 16 bit image
  ***************************************************************************************/
  void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, const uint16_t *data)
  {
  #ifdef ESP32
    pushImage(x, y, w, h, (uint16_t*)data);
  #else
    // Partitioned memory FLASH processor
    if ((x >= _width) || (y >= _height)) return;
  
    int32_t dx = 0;
    int32_t dy = 0;
    int32_t dw = w;
    int32_t dh = h;
  
    if (x < 0) { dw += x; dx = -x; x = 0; }
    if (y < 0) { dh += y; dy = -y; y = 0; }
    
    if ((x + dw) > _width ) dw = _width  - x;
    if ((y + dh) > _height) dh = _height - y;
  
    if (dw < 1 || dh < 1) return;
  
    spi_begin();
    inTransaction = true;
  
    data += dx + dy * w;
  
    uint16_t  buffer[64];
    uint16_t* pix_buffer = buffer;
  
    setWindow(x, y, x + dw - 1, y + dh - 1);
  
    // Work out the number whole buffers to send
    uint16_t nb = (dw * dh) / 64;
  
    // Fill and send "nb" buffers to TFT
    for (int32_t i = 0; i < nb; i++) {
      for (int32_t j = 0; j < 64; j++) {
        pix_buffer[j] = pgm_read_word(&data[i * 64 + j]);
      }
      pushColors(pix_buffer, 64, _swapBytes);
    }
  
    // Work out number of pixels not yet sent
    uint16_t np = (dw * dh) % 64;
  
    // Send any partial buffer left over
    if (np) {
      for (int32_t i = 0; i < np; i++)
      {
        pix_buffer[i] = pgm_read_word(&data[nb * 64 + i]);
      }
      pushColors(pix_buffer, np, _swapBytes);
    }
  
    inTransaction = false;
    spi_end();
  #endif // if ESP32 else ESP8266 check
  }
  
  
  /***************************************************************************************
  ** Function name:           pushImage - for FLASH (PROGMEM) stored images
  ** Description:             plot 16 bit image with 1 colour being transparent
  ***************************************************************************************/
  void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, const uint16_t *data, uint16_t transp)
  {
  #ifdef ESP32
    pushImage(x, y, w, h, (uint16_t*) data, transp);
  #else
    // Partitioned memory FLASH processor
    if ((x >= _width) || (y >= (int32_t)_height)) return;
  
    int32_t dx = 0;
    int32_t dy = 0;
    int32_t dw = w;
    int32_t dh = h;
  
    if (x < 0) { dw += x; dx = -x; x = 0; }
    if (y < 0) { dh += y; dy = -y; y = 0; }
    
    if ((x + dw) > _width ) dw = _width  - x;
    if ((y + dh) > _height) dh = _height - y;
  
    if (dw < 1 || dh < 1) return;
  
    spi_begin();
    inTransaction = true;
  
    data += dx + dy * w;
  
    int32_t xe = x + dw - 1, ye = y + dh - 1;
  
    uint16_t  lineBuf[dw];
  
    if (!_swapBytes) transp = transp >> 8 | transp << 8;
  
    while (dh--)
    {
      int32_t len = dw;
      uint16_t* ptr = (uint16_t*)data;
      int32_t px = x;
      boolean move = true;
  
      uint16_t np = 0;
  
      while (len--)
      {
        uint16_t color = pgm_read_word(ptr);
        if (transp != color)
        {
          if (move) { move = false; setWindow(px, y, xe, ye); }
          lineBuf[np] = color;
          np++;
        }
        else
        {
          move = true;
          if (np)
          {
             pushColors(lineBuf, np, _swapBytes);
             np = 0;
          }
        }
        px++;
        ptr++;
      }
      if (np) pushColors(lineBuf, np, _swapBytes);
  
      y++;
      data += w;
    }
  
    inTransaction = false;
    spi_end();
  #endif // if ESP32 else ESP8266 check
  }
  
  
  /***************************************************************************************
  ** Function name:           pushImage
  ** Description:             plot 8 bit image or sprite using a line buffer
  ***************************************************************************************/
  void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t *data, bool bpp8)
  {
    if ((x >= _width) || (y >= (int32_t)_height)) return;
  
    int32_t dx = 0;
    int32_t dy = 0;
    int32_t dw = w;
    int32_t dh = h;
  
    if (x < 0) { dw += x; dx = -x; x = 0; }
    if (y < 0) { dh += y; dy = -y; y = 0; }
    
    if ((x + dw) > _width ) dw = _width  - x;
    if ((y + dh) > _height) dh = _height - y;
  
    if (dw < 1 || dh < 1) return;
  
    spi_begin();
    inTransaction = true;
  
    setWindow(x, y, x + dw - 1, y + dh - 1); // Sets CS low and sent RAMWR
  
    // Line buffer makes plotting faster
    uint16_t  lineBuf[dw];
  
    if (bpp8)
    {
      uint8_t  blue[] = {0, 11, 21, 31}; // blue 2 to 5 bit colour lookup table
  
      _lastColor = -1; // Set to illegal value
  
      // Used to store last shifted colour
      uint8_t msbColor = 0;
      uint8_t lsbColor = 0;
  
      data += dx + dy * w;
      while (dh--)
      {
        uint32_t len = dw;
        uint8_t* ptr = data;
        uint8_t* linePtr = (uint8_t*)lineBuf;
  
        while(len--)
        {
          uint32_t color = *ptr++;
  
          // Shifts are slow so check if colour has changed first
          if (color != _lastColor) {
            //          =====Green=====     ===============Red==============
            msbColor = (color & 0x1C)>>2 | (color & 0xC0)>>3 | (color & 0xE0);
            //          =====Green=====    =======Blue======
            lsbColor = (color & 0x1C)<<3 | blue[color & 0x03];
            _lastColor = color;
          }
  
         *linePtr++ = msbColor;
         *linePtr++ = lsbColor;
        }
  
        pushColors(lineBuf, dw, false);
  
        data += w;
      }
    }
    else
    {
      while (dh--)
      {
        w =  (w+7) & 0xFFF8;
  
        int32_t len = dw;
        uint8_t* ptr = data;
        uint8_t* linePtr = (uint8_t*)lineBuf;
        uint8_t  bits = 8;
        while(len>0)
        {
          if (len < 8) bits = len;
          uint32_t xp = dx;
          for (uint16_t i = 0; i < bits; i++)
          {
            uint8_t col = (ptr[(xp + dy * w)>>3] << (xp & 0x7)) & 0x80;
            if (col) {*linePtr++ = bitmap_fg>>8; *linePtr++ = (uint8_t) bitmap_fg;}
            else     {*linePtr++ = bitmap_bg>>8; *linePtr++ = (uint8_t) bitmap_bg;}
            //if (col) drawPixel((dw-len)+xp,h-dh,bitmap_fg);
            //else     drawPixel((dw-len)+xp,h-dh,bitmap_bg);
            xp++;
          }
          ptr++;
          len -= 8;
        }
  
        pushColors(lineBuf, dw, false);
  
        dy++;
      }
    }
  
    inTransaction = false;
    spi_end();
  }
  
  
  /***************************************************************************************
  ** Function name:           pushImage
  ** Description:             plot 8 or 1 bit image or sprite with a transparent colour
  ***************************************************************************************/
  void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t *data, uint8_t transp, bool bpp8)
  {
    if ((x >= _width) || (y >= _height)) return;
  
    int32_t dx = 0;
    int32_t dy = 0;
    int32_t dw = w;
    int32_t dh = h;
  
    if (x < 0) { dw += x; dx = -x; x = 0; }
    if (y < 0) { dh += y; dy = -y; y = 0; }
    
    if ((x + dw) > _width ) dw = _width  - x;
    if ((y + dh) > _height) dh = _height - y;
  
    if (dw < 1 || dh < 1) return;
  
    spi_begin();
    inTransaction = true;
  
    int32_t xe = x + dw - 1, ye = y + dh - 1;
  
    // Line buffer makes plotting faster
    uint16_t  lineBuf[dw];
  
    if (bpp8)
    {
      data += dx + dy * w;
  
      uint8_t  blue[] = {0, 11, 21, 31}; // blue 2 to 5 bit colour lookup table
  
      _lastColor = -1; // Set to illegal value
  
      // Used to store last shifted colour
      uint8_t msbColor = 0;
      uint8_t lsbColor = 0;
  
      //int32_t spx = x, spy = y;
  
      while (dh--)
      {
        int32_t len = dw;
        uint8_t* ptr = data;
        uint8_t* linePtr = (uint8_t*)lineBuf;
  
        int32_t px = x;
        boolean move = true;
        uint16_t np = 0;
  
        while (len--)
        {
          if (transp != *ptr)
          {
            if (move) { move = false; setWindow(px, y, xe, ye);}
            uint8_t color = *ptr;
  
            // Shifts are slow so check if colour has changed first
            if (color != _lastColor) {
              //          =====Green=====     ===============Red==============
              msbColor = (color & 0x1C)>>2 | (color & 0xC0)>>3 | (color & 0xE0);
              //          =====Green=====    =======Blue======
              lsbColor = (color & 0x1C)<<3 | blue[color & 0x03];
              _lastColor = color;
            }
            *linePtr++ = msbColor;
            *linePtr++ = lsbColor;
            np++;
          }
          else
          {
            move = true;
            if (np)
            {
              pushColors(lineBuf, np, false);
              linePtr = (uint8_t*)lineBuf;
              np = 0;
            }
          }
          px++;
          ptr++;
        }
  
        if (np) pushColors(lineBuf, np, false);
  
        y++;
        data += w;
      }
    }
    else
    {
      w =  (w+7) & 0xFFF8;
      while (dh--)
      {
        int32_t px = x;
        boolean move = true;
        uint16_t np = 0;
        int32_t len = dw;
        uint8_t* ptr = data;
        uint8_t  bits = 8;
        while(len>0)
        {
          if (len < 8) bits = len;
          uint32_t xp = dx;
          uint32_t yp = (dy * w)>>3;
          for (uint16_t i = 0; i < bits; i++)
          {
            //uint8_t col = (ptr[(xp + dy * w)>>3] << (xp & 0x7)) & 0x80;
            if ((ptr[(xp>>3) + yp] << (xp & 0x7)) & 0x80)
            {
              if (move)
              {
                move = false;
                setWindow(px, y, xe, ye);
              }
              np++;
            }
            else
            {
              if (np)
              {
                pushColor(bitmap_fg, np);
                np = 0;
                move = true;
              }
            }
            px++;
            xp++;
          }
          ptr++;
          len -= 8;
        }
        if (np) pushColor(bitmap_fg, np);
        y++;
        dy++;
      }
    }
  
    inTransaction = false;
    spi_end();
  }
  
  
  /***************************************************************************************
  ** Function name:           setSwapBytes
  ** Description:             Used by 16 bit pushImage() to swap byte order in colours
  ***************************************************************************************/
  void TFT_eSPI::setSwapBytes(bool swap)
  {
    _swapBytes = swap;
  }
  
  
  /***************************************************************************************
  ** Function name:           getSwapBytes
  ** Description:             Return the swap byte order for colours
  ***************************************************************************************/
  bool TFT_eSPI::getSwapBytes(void)
  {
    return _swapBytes;
  }
  
  /***************************************************************************************
  ** Function name:           read rectangle (for SPI Interface II i.e. IM [3:0] = "1101")
  ** Description:             Read RGB pixel colours from a defined area
  ***************************************************************************************/
  // If w and h are 1, then 1 pixel is read, *data array size must be 3 bytes per pixel
  void  TFT_eSPI::readRectRGB(int32_t x0, int32_t y0, int32_t w, int32_t h, uint8_t *data)
  {
  #if defined(ESP32_PARALLEL)
  
    // ESP32 parallel bus supported yet
  
  #else  // Not ESP32_PARALLEL
  
    spi_begin_read();
  
    readAddrWindow(x0, y0, w, h); // Sets CS low
  
    #ifdef TFT_SDA_READ
      begin_SDA_Read();
    #endif
  
    // Dummy read to throw away don't care value
    tft_Read_8();
  
    // Read window pixel 24 bit RGB values, buffer must be set in sketch to 3 * w * h
    uint32_t len = w * h;
    while (len--) {
  
    #if !defined (ILI9488_DRIVER)
  
      // Read the 3 RGB bytes, colour is actually only in the top 6 bits of each byte
      // as the TFT stores colours as 18 bits
      *data++ = tft_Read_8();
      *data++ = tft_Read_8();
      *data++ = tft_Read_8();
  
    #else
  
      // The 6 colour bits are in MS 6 bits of each byte, but the ILI9488 needs an extra clock pulse
      // so bits appear shifted right 1 bit, so mask the middle 6 bits then shift 1 place left
      *data++ = (tft_Read_8()&0x7E)<<1;
      *data++ = (tft_Read_8()&0x7E)<<1;
      *data++ = (tft_Read_8()&0x7E)<<1;
  
    #endif
  
    }
  
    CS_H;
  
    #ifdef TFT_SDA_READ
      end_SDA_Read();
    #endif
  
    spi_end_read();
  
  #endif
  }
  
  
  /***************************************************************************************
  ** Function name:           drawCircle
  ** Description:             Draw a circle outline
  ***************************************************************************************/
  // Optimised midpoint circle algorithm
  void TFT_eSPI::drawCircle(int32_t x0, int32_t y0, int32_t r, uint32_t color)
  {
    int32_t  x  = 0;
    int32_t  dx = 1;
    int32_t  dy = r+r;
    int32_t  p  = -(r>>1);
  
    //spi_begin();          // Sprite class can use this function, avoiding spi_begin()
    inTransaction = true;
  
    // These are ordered to minimise coordinate changes in x or y
    // drawPixel can then send fewer bounding box commands
    drawPixel(x0 + r, y0, color);
    drawPixel(x0 - r, y0, color);
    drawPixel(x0, y0 - r, color);
    drawPixel(x0, y0 + r, color);
  
    while(x<r){
  
      if(p>=0) {
        dy-=2;
        p-=dy;
        r--;
      }
  
      dx+=2;
      p+=dx;
  
      x++;
  
      // These are ordered to minimise coordinate changes in x or y
      // drawPixel can then send fewer bounding box commands
      drawPixel(x0 + x, y0 + r, color);
      drawPixel(x0 - x, y0 + r, color);
      drawPixel(x0 - x, y0 - r, color);
      drawPixel(x0 + x, y0 - r, color);
  
      drawPixel(x0 + r, y0 + x, color);
      drawPixel(x0 - r, y0 + x, color);
      drawPixel(x0 - r, y0 - x, color);
      drawPixel(x0 + r, y0 - x, color);
    }
  
    inTransaction = false;
    spi_end();              // Does nothing if Sprite class uses this function
  }
  
  
  /***************************************************************************************
  ** Function name:           drawCircleHelper
  ** Description:             Support function for circle drawing
  ***************************************************************************************/
  void TFT_eSPI::drawCircleHelper( int32_t x0, int32_t y0, int32_t r, uint8_t cornername, uint32_t color)
  {
    int32_t f     = 1 - r;
    int32_t ddF_x = 1;
    int32_t ddF_y = -2 * r;
    int32_t x     = 0;
  
    while (x < r) {
      if (f >= 0) {
        r--;
        ddF_y += 2;
        f     += ddF_y;
      }
      x++;
      ddF_x += 2;
      f     += ddF_x;
      if (cornername & 0x4) {
        drawPixel(x0 + x, y0 + r, color);
        drawPixel(x0 + r, y0 + x, color);
      }
      if (cornername & 0x2) {
        drawPixel(x0 + x, y0 - r, color);
        drawPixel(x0 + r, y0 - x, color);
      }
      if (cornername & 0x8) {
        drawPixel(x0 - r, y0 + x, color);
        drawPixel(x0 - x, y0 + r, color);
      }
      if (cornername & 0x1) {
        drawPixel(x0 - r, y0 - x, color);
        drawPixel(x0 - x, y0 - r, color);
      }
    }
  }
  
  
  /***************************************************************************************
  ** Function name:           fillCircle
  ** Description:             draw a filled circle
  ***************************************************************************************/
  // Optimised midpoint circle algorithm, changed to horizontal lines (faster in sprites)
  void TFT_eSPI::fillCircle(int32_t x0, int32_t y0, int32_t r, uint32_t color)
  {
    int32_t  x  = 0;
    int32_t  dx = 1;
    int32_t  dy = r+r;
    int32_t  p  = -(r>>1);
  
    //spi_begin();          // Sprite class can use this function, avoiding spi_begin()
    inTransaction = true;
  
    drawFastHLine(x0 - r, y0, dy+1, color);
  
    while(x<r){
  
      if(p>=0) {
        dy-=2;
        p-=dy;
        r--;
      }
  
      dx+=2;
      p+=dx;
  
      x++;
  
      drawFastHLine(x0 - r, y0 + x, 2 * r+1, color);
      drawFastHLine(x0 - r, y0 - x, 2 * r+1, color);
      drawFastHLine(x0 - x, y0 + r, 2 * x+1, color);
      drawFastHLine(x0 - x, y0 - r, 2 * x+1, color);
  
    }
  
    inTransaction = false;  
    spi_end();              // Does nothing if Sprite class uses this function
  }
  
  
  /***************************************************************************************
  ** Function name:           fillCircleHelper
  ** Description:             Support function for filled circle drawing
  ***************************************************************************************/
  // Used to support drawing roundrects, changed to horizontal lines (faster in sprites)
  void TFT_eSPI::fillCircleHelper(int32_t x0, int32_t y0, int32_t r, uint8_t cornername, int32_t delta, uint32_t color)
  {
    int32_t f     = 1 - r;
    int32_t ddF_x = 1;
    int32_t ddF_y = -r - r;
    int32_t y     = 0;
  
    delta++;
    while (y < r) {
      if (f >= 0) {
        r--;
        ddF_y += 2;
        f     += ddF_y;
      }
      y++;
      //x++;
      ddF_x += 2;
      f     += ddF_x;
  
      if (cornername & 0x1)
      {
        drawFastHLine(x0 - r, y0 + y, r + r + delta, color);
        drawFastHLine(x0 - y, y0 + r, y + y + delta, color);
      }
      if (cornername & 0x2) {
        drawFastHLine(x0 - r, y0 - y, r + r + delta, color); // 11995, 1090
        drawFastHLine(x0 - y, y0 - r, y + y + delta, color);
      }
    }
  }
  
  
  /***************************************************************************************
  ** Function name:           drawEllipse
  ** Description:             Draw a ellipse outline
  ***************************************************************************************/
  void TFT_eSPI::drawEllipse(int16_t x0, int16_t y0, int32_t rx, int32_t ry, uint16_t color)
  {
    if (rx<2) return;
    if (ry<2) return;
    int32_t x, y;
    int32_t rx2 = rx * rx;
    int32_t ry2 = ry * ry;
    int32_t fx2 = 4 * rx2;
    int32_t fy2 = 4 * ry2;
    int32_t s;
  
    //spi_begin();          // Sprite class can use this function, avoiding spi_begin()
    inTransaction = true;
  
    for (x = 0, y = ry, s = 2*ry2+rx2*(1-2*ry); ry2*x <= rx2*y; x++)
    {
      // These are ordered to minimise coordinate changes in x or y
      // drawPixel can then send fewer bounding box commands
      drawPixel(x0 + x, y0 + y, color);
      drawPixel(x0 - x, y0 + y, color);
      drawPixel(x0 - x, y0 - y, color);
      drawPixel(x0 + x, y0 - y, color);
      if (s >= 0)
      {
        s += fx2 * (1 - y);
        y--;
      }
      s += ry2 * ((4 * x) + 6);
    }
  
    for (x = rx, y = 0, s = 2*rx2+ry2*(1-2*rx); rx2*y <= ry2*x; y++)
    {
      // These are ordered to minimise coordinate changes in x or y
      // drawPixel can then send fewer bounding box commands
      drawPixel(x0 + x, y0 + y, color);
      drawPixel(x0 - x, y0 + y, color);
      drawPixel(x0 - x, y0 - y, color);
      drawPixel(x0 + x, y0 - y, color);
      if (s >= 0)
      {
        s += fy2 * (1 - x);
        x--;
      }
      s += rx2 * ((4 * y) + 6);
    }
  
    inTransaction = false;
    spi_end();              // Does nothing if Sprite class uses this function
  }
  
  
  /***************************************************************************************
  ** Function name:           fillEllipse
  ** Description:             draw a filled ellipse
  ***************************************************************************************/
  void TFT_eSPI::fillEllipse(int16_t x0, int16_t y0, int32_t rx, int32_t ry, uint16_t color)
  {
    if (rx<2) return;
    if (ry<2) return;
    int32_t x, y;
    int32_t rx2 = rx * rx;
    int32_t ry2 = ry * ry;
    int32_t fx2 = 4 * rx2;
    int32_t fy2 = 4 * ry2;
    int32_t s;
  
    //spi_begin();          // Sprite class can use this function, avoiding spi_begin()
    inTransaction = true;
  
    for (x = 0, y = ry, s = 2*ry2+rx2*(1-2*ry); ry2*x <= rx2*y; x++)
    {
      drawFastHLine(x0 - x, y0 - y, x + x + 1, color);
      drawFastHLine(x0 - x, y0 + y, x + x + 1, color);
  
      if (s >= 0)
      {
        s += fx2 * (1 - y);
        y--;
      }
      s += ry2 * ((4 * x) + 6);
    }
  
    for (x = rx, y = 0, s = 2*rx2+ry2*(1-2*rx); rx2*y <= ry2*x; y++)
    {
      drawFastHLine(x0 - x, y0 - y, x + x + 1, color);
      drawFastHLine(x0 - x, y0 + y, x + x + 1, color);
  
      if (s >= 0)
      {
        s += fy2 * (1 - x);
        x--;
      }
      s += rx2 * ((4 * y) + 6);
    }
  
    inTransaction = false;
    spi_end();              // Does nothing if Sprite class uses this function
  }
  
  
  /***************************************************************************************
  ** Function name:           fillScreen
  ** Description:             Clear the screen to defined colour
  ***************************************************************************************/
  void TFT_eSPI::fillScreen(uint32_t color)
  {
    fillRect(0, 0, _width, _height, color);
  }
  
  
  /***************************************************************************************
  ** Function name:           drawRect
  ** Description:             Draw a rectangle outline
  ***************************************************************************************/
  // Draw a rectangle
  void TFT_eSPI::drawRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color)
  {
    //spi_begin();          // Sprite class can use this function, avoiding spi_begin()
    inTransaction = true;
  
    drawFastHLine(x, y, w, color);
    drawFastHLine(x, y + h - 1, w, color);
    // Avoid drawing corner pixels twice
    drawFastVLine(x, y+1, h-2, color);
    drawFastVLine(x + w - 1, y+1, h-2, color);
  
    inTransaction = false;
    spi_end();              // Does nothing if Sprite class uses this function
  }
  
  
  /***************************************************************************************
  ** Function name:           drawRoundRect
  ** Description:             Draw a rounded corner rectangle outline
  ***************************************************************************************/
  // Draw a rounded rectangle
  void TFT_eSPI::drawRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t r, uint32_t color)
  {
    //spi_begin();          // Sprite class can use this function, avoiding spi_begin()
    inTransaction = true;
  
    // smarter version
    drawFastHLine(x + r  , y    , w - r - r, color); // Top
    drawFastHLine(x + r  , y + h - 1, w - r - r, color); // Bottom
    drawFastVLine(x    , y + r  , h - r - r, color); // Left
    drawFastVLine(x + w - 1, y + r  , h - r - r, color); // Right
    // draw four corners
    drawCircleHelper(x + r    , y + r    , r, 1, color);
    drawCircleHelper(x + w - r - 1, y + r    , r, 2, color);
    drawCircleHelper(x + w - r - 1, y + h - r - 1, r, 4, color);
    drawCircleHelper(x + r    , y + h - r - 1, r, 8, color);
    
    inTransaction = false;
    spi_end();              // Does nothing if Sprite class uses this function
  }
  
  
  /***************************************************************************************
  ** Function name:           fillRoundRect
  ** Description:             Draw a rounded corner filled rectangle
  ***************************************************************************************/
  // Fill a rounded rectangle, changed to horizontal lines (faster in sprites)
  void TFT_eSPI::fillRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t r, uint32_t color)
  {
    //spi_begin();          // Sprite class can use this function, avoiding spi_begin()
    inTransaction = true;
  
    // smarter version
    fillRect(x, y + r, w, h - r - r, color);
  
    // draw four corners
    fillCircleHelper(x + r, y + h - r - 1, r, 1, w - r - r - 1, color);
    fillCircleHelper(x + r    , y + r, r, 2, w - r - r - 1, color);
    
    inTransaction = false;
    spi_end();              // Does nothing if Sprite class uses this function
  }
  
  
  /***************************************************************************************
  ** Function name:           drawTriangle
  ** Description:             Draw a triangle outline using 3 arbitrary points
  ***************************************************************************************/
  // Draw a triangle
  void TFT_eSPI::drawTriangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t color)
  {
    //spi_begin();          // Sprite class can use this function, avoiding spi_begin()
    inTransaction = true;
  
    drawLine(x0, y0, x1, y1, color);
    drawLine(x1, y1, x2, y2, color);
    drawLine(x2, y2, x0, y0, color);
  
    inTransaction = false;
    spi_end();              // Does nothing if Sprite class uses this function
  }
  
  
  /***************************************************************************************
  ** Function name:           fillTriangle
  ** Description:             Draw a filled triangle using 3 arbitrary points
  ***************************************************************************************/
  // Fill a triangle - original Adafruit function works well and code footprint is small
  void TFT_eSPI::fillTriangle ( int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t color)
  {
    int32_t a, b, y, last;
  
    // Sort coordinates by Y order (y2 >= y1 >= y0)
    if (y0 > y1) {
      swap_coord(y0, y1); swap_coord(x0, x1);
    }
    if (y1 > y2) {
      swap_coord(y2, y1); swap_coord(x2, x1);
    }
    if (y0 > y1) {
      swap_coord(y0, y1); swap_coord(x0, x1);
    }
  
    if (y0 == y2) { // Handle awkward all-on-same-line case as its own thing
      a = b = x0;
      if (x1 < a)      a = x1;
      else if (x1 > b) b = x1;
      if (x2 < a)      a = x2;
      else if (x2 > b) b = x2;
      drawFastHLine(a, y0, b - a + 1, color);
      return;
    }
  
    //spi_begin();          // Sprite class can use this function, avoiding spi_begin()
    inTransaction = true;
  
    int32_t
    dx01 = x1 - x0,
    dy01 = y1 - y0,
    dx02 = x2 - x0,
    dy02 = y2 - y0,
    dx12 = x2 - x1,
    dy12 = y2 - y1,
    sa   = 0,
    sb   = 0;
  
    // For upper part of triangle, find scanline crossings for segments
    // 0-1 and 0-2.  If y1=y2 (flat-bottomed triangle), the scanline y1
    // is included here (and second loop will be skipped, avoiding a /0
    // error there), otherwise scanline y1 is skipped here and handled
    // in the second loop...which also avoids a /0 error here if y0=y1
    // (flat-topped triangle).
    if (y1 == y2) last = y1;  // Include y1 scanline
    else         last = y1 - 1; // Skip it
  
    for (y = y0; y <= last; y++) {
      a   = x0 + sa / dy01;
      b   = x0 + sb / dy02;
      sa += dx01;
      sb += dx02;
  
      if (a > b) swap_coord(a, b);
      drawFastHLine(a, y, b - a + 1, color);
    }
  
    // For lower part of triangle, find scanline crossings for segments
    // 0-2 and 1-2.  This loop is skipped if y1=y2.
    sa = dx12 * (y - y1);
    sb = dx02 * (y - y0);
    for (; y <= y2; y++) {
      a   = x1 + sa / dy12;
      b   = x0 + sb / dy02;
      sa += dx12;
      sb += dx02;
  
      if (a > b) swap_coord(a, b);
      drawFastHLine(a, y, b - a + 1, color);
    }
  
    inTransaction = false;
    spi_end();              // Does nothing if Sprite class uses this function
  }
  
  
  /***************************************************************************************
  ** Function name:           drawBitmap
  ** Description:             Draw an image stored in an array on the TFT
  ***************************************************************************************/
  void TFT_eSPI::drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color)
  {
    //spi_begin();          // Sprite class can use this function, avoiding spi_begin()
    inTransaction = true;
  
    int32_t i, j, byteWidth = (w + 7) / 8;
  
    for (j = 0; j < h; j++) {
      for (i = 0; i < w; i++ ) {
        if (pgm_read_byte(bitmap + j * byteWidth + i / 8) & (128 >> (i & 7))) {
          drawPixel(x + i, y + j, color);
        }
      }
    }
  
    inTransaction = false;
    spi_end();              // Does nothing if Sprite class uses this function
  }
  
  
  /***************************************************************************************
  ** Function name:           drawXBitmap
  ** Description:             Draw an image stored in an XBM array onto the TFT
  ***************************************************************************************/
  void TFT_eSPI::drawXBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color)
  {
    //spi_begin();          // Sprite class can use this function, avoiding spi_begin()
    inTransaction = true;
  
    int32_t i, j, byteWidth = (w + 7) / 8;
  
    for (j = 0; j < h; j++) {
      for (i = 0; i < w; i++ ) {
        if (pgm_read_byte(bitmap + j * byteWidth + i / 8) & (1 << (i & 7))) {
          drawPixel(x + i, y + j, color);
        }
      }
    }
  
    inTransaction = false;
    spi_end();              // Does nothing if Sprite class uses this function
  }
  
  
  /***************************************************************************************
  ** Function name:           drawXBitmap
  ** Description:             Draw an XBM image with foreground and background colors
  ***************************************************************************************/
  void TFT_eSPI::drawXBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color, uint16_t bgcolor)
  {
    //spi_begin();          // Sprite class can use this function, avoiding spi_begin()
    inTransaction = true;
  
    int32_t i, j, byteWidth = (w + 7) / 8;
  
    for (j = 0; j < h; j++) {
      for (i = 0; i < w; i++ ) {
        if (pgm_read_byte(bitmap + j * byteWidth + i / 8) & (1 << (i & 7)))
             drawPixel(x + i, y + j,   color);
        else drawPixel(x + i, y + j, bgcolor);
      }
    }
  
    inTransaction = false;
    spi_end();              // Does nothing if Sprite class uses this function
  }
  
  
  /***************************************************************************************
  ** Function name:           setCursor
  ** Description:             Set the text cursor x,y position
  ***************************************************************************************/
  void TFT_eSPI::setCursor(int16_t x, int16_t y)
  {
    cursor_x = x;
    cursor_y = y;
  }
  
  
  /***************************************************************************************
  ** Function name:           setCursor
  ** Description:             Set the text cursor x,y position and font
  ***************************************************************************************/
  void TFT_eSPI::setCursor(int16_t x, int16_t y, uint8_t font)
  {
    textfont = font;
    cursor_x = x;
    cursor_y = y;
  }
  
  
  /***************************************************************************************
  ** Function name:           getCursorX
  ** Description:             Get the text cursor x position
  ***************************************************************************************/
  int16_t TFT_eSPI::getCursorX(void)
  {
    return cursor_x;
  }
  
  /***************************************************************************************
  ** Function name:           getCursorY
  ** Description:             Get the text cursor y position
  ***************************************************************************************/
  int16_t TFT_eSPI::getCursorY(void)
  {
    return cursor_y;
  }
  
  
  /***************************************************************************************
  ** Function name:           setTextSize
  ** Description:             Set the text size multiplier
  ***************************************************************************************/
  void TFT_eSPI::setTextSize(uint8_t s)
  {
    if (s>7) s = 7; // Limit the maximum size multiplier so byte variables can be used for rendering
    textsize = (s > 0) ? s : 1; // Don't allow font size 0
  }
  
  
  /***************************************************************************************
  ** Function name:           setTextColor
  ** Description:             Set the font foreground colour (background is transparent)
  ***************************************************************************************/
  void TFT_eSPI::setTextColor(uint16_t c)
  {
    // For 'transparent' background, we'll set the bg
    // to the same as fg instead of using a flag
    textcolor = textbgcolor = c;
  }
  
  
  /***************************************************************************************
  ** Function name:           setTextColor
  ** Description:             Set the font foreground and background colour
  ***************************************************************************************/
  void TFT_eSPI::setTextColor(uint16_t c, uint16_t b)
  {
    textcolor   = c;
    textbgcolor = b;
  }
  
  
  /***************************************************************************************
  ** Function name:           setPivot
  ** Description:             Set the pivot point on the TFT
  *************************************************************************************x*/
  void TFT_eSPI::setPivot(int16_t x, int16_t y)
  {
    _xpivot = x;
    _ypivot = y;
  }
  
  
  /***************************************************************************************
  ** Function name:           getPivotX
  ** Description:             Get the x pivot position
  ***************************************************************************************/
  int16_t TFT_eSPI::getPivotX(void)
  {
    return _xpivot;
  }
  
  
  /***************************************************************************************
  ** Function name:           getPivotY
  ** Description:             Get the y pivot position
  ***************************************************************************************/
  int16_t TFT_eSPI::getPivotY(void)
  {
    return _ypivot;
  }
  
  
  /***************************************************************************************
  ** Function name:           setBitmapColor
  ** Description:             Set the foreground foreground and background colour
  ***************************************************************************************/
  void TFT_eSPI::setBitmapColor(uint16_t c, uint16_t b)
  {
    if (c == b) b = ~c;
    bitmap_fg = c;
    bitmap_bg = b;
  }
  
  
  /***************************************************************************************
  ** Function name:           setTextWrap
  ** Description:             Define if text should wrap at end of line
  ***************************************************************************************/
  void TFT_eSPI::setTextWrap(boolean wrapX, boolean wrapY)
  {
    textwrapX = wrapX;
    textwrapY = wrapY;
  }
  
  
  /***************************************************************************************
  ** Function name:           setTextDatum
  ** Description:             Set the text position reference datum
  ***************************************************************************************/
  void TFT_eSPI::setTextDatum(uint8_t d)
  {
    textdatum = d;
  }
  
  
  /***************************************************************************************
  ** Function name:           setTextPadding
  ** Description:             Define padding width (aids erasing old text and numbers)
  ***************************************************************************************/
  void TFT_eSPI::setTextPadding(uint16_t x_width)
  {
    padX = x_width;
  }
  
  
  /***************************************************************************************
  ** Function name:           getRotation
  ** Description:             Return the rotation value (as used by setRotation())
  ***************************************************************************************/
  uint8_t TFT_eSPI::getRotation(void)
  {
    return rotation;
  }
  
  /***************************************************************************************
  ** Function name:           getTextDatum
  ** Description:             Return the text datum value (as used by setTextDatum())
  ***************************************************************************************/
  uint8_t TFT_eSPI::getTextDatum(void)
  {
    return textdatum;
  }
  
  
  /***************************************************************************************
  ** Function name:           width
  ** Description:             Return the pixel width of display (per current rotation)
  ***************************************************************************************/
  // Return the size of the display (per current rotation)
  int16_t TFT_eSPI::width(void)
  {
    return _width;
  }
  
  
  /***************************************************************************************
  ** Function name:           height
  ** Description:             Return the pixel height of display (per current rotation)
  ***************************************************************************************/
  int16_t TFT_eSPI::height(void)
  {
    return _height;
  }
  
  
  /***************************************************************************************
  ** Function name:           textWidth
  ** Description:             Return the width in pixels of a string in a given font
  ***************************************************************************************/
  int16_t TFT_eSPI::textWidth(const String& string)
  {
    int16_t len = string.length() + 2;
    char buffer[len];
    string.toCharArray(buffer, len);
    return textWidth(buffer, textfont);
  }
  
  int16_t TFT_eSPI::textWidth(const String& string, uint8_t font)
  {
    int16_t len = string.length() + 2;
    char buffer[len];
    string.toCharArray(buffer, len);
    return textWidth(buffer, font);
  }
  
  int16_t TFT_eSPI::textWidth(const char *string)
  {
    return textWidth(string, textfont);
  }
  
  int16_t TFT_eSPI::textWidth(const char *string, uint8_t font)
  {
    int32_t str_width = 0;
    uint16_t uniCode  = 0;
  
  #ifdef SMOOTH_FONT
    if(fontLoaded)
    {
      while (*string)
      {
        uniCode = decodeUTF8(*string++);
        if (uniCode)
        {
          if (uniCode == 0x20) str_width += gFont.spaceWidth;
          else
          {
            uint16_t gNum = 0;
            bool found = getUnicodeIndex(uniCode, &gNum);
            if (found)
            {
              if(str_width == 0 && gdX[gNum] < 0) str_width -= gdX[gNum];
              if (*string || isDigits) str_width += gxAdvance[gNum];
              else str_width += (gdX[gNum] + gWidth[gNum]);
            }
            else str_width += gFont.spaceWidth + 1;
          }
        }
      }
      isDigits = false;
      return str_width;
    }
  #endif
  
    if (font>1 && font<9)
    {
      char *widthtable = (char *)pgm_read_dword( &(fontdata[font].widthtbl ) ) - 32; //subtract the 32 outside the loop
  
      while (*string)
      {
        uniCode = *(string++);
        if (uniCode > 31 && uniCode < 128)
        str_width += pgm_read_byte( widthtable + uniCode); // Normally we need to subtract 32 from uniCode
        else str_width += pgm_read_byte( widthtable + 32); // Set illegal character = space width
      }
  
    }
    else
    {
  
  #ifdef LOAD_GFXFF
      if(gfxFont) // New font
      {
        while (*string)
        {
          uniCode = decodeUTF8(*string++);
  
          bool is_in_block_flag = false;
          #ifdef USE_M5_FONT_CREATOR
          int32_t index = -1;
          index = getUnicodeFontIndex(uniCode);
          if (index != -1) {
            is_in_block_flag = true;
            uniCode = index;
          }
          #else
          if ((uniCode >= pgm_read_word(&gfxFont->first)) && (uniCode <= pgm_read_word(&gfxFont->last )))
          {
            is_in_block_flag = true;
            uniCode -= pgm_read_word(&gfxFont->first);
          }
          #endif
  
          if (is_in_block_flag == true)
          //if ((uniCode >= pgm_read_word(&gfxFont->first)) && (uniCode <= pgm_read_word(&gfxFont->last )))
          {
            //uniCode -= pgm_read_word(&gfxFont->first);
            GFXglyph *glyph  = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[uniCode]);
            // If this is not the  last character or is a digit then use xAdvance
            if (*string  || isDigits) str_width += pgm_read_byte(&glyph->xAdvance);
            // Else use the offset plus width since this can be bigger than xAdvance
            else str_width += ((int8_t)pgm_read_byte(&glyph->xOffset) + pgm_read_byte(&glyph->width));
          }
        }
      }
      else
  #endif
      {
  #ifdef LOAD_GLCD
        while (*string++) str_width += 6;
  #endif
      }
    }
    isDigits = false;
    return str_width * textsize;
  }
  
  
  /***************************************************************************************
  ** Function name:           fontsLoaded
  ** Description:             return an encoded 16 bit value showing the fonts loaded
  ***************************************************************************************/
  // Returns a value showing which fonts are loaded (bit N set =  Font N loaded)
  
  uint16_t TFT_eSPI::fontsLoaded(void)
  {
    return fontsloaded;
  }
  
  
  /***************************************************************************************
  ** Function name:           fontHeight
  ** Description:             return the height of a font (yAdvance for free fonts)
  ***************************************************************************************/
  int16_t TFT_eSPI::fontHeight(int16_t font)
  {
  #ifdef SMOOTH_FONT
    if(fontLoaded) return gFont.yAdvance;
  #endif
  
  #ifdef LOAD_GFXFF
    if (font==1)
    {
      if(gfxFont) // New font
      {
        return pgm_read_byte(&gfxFont->yAdvance) * textsize;
      }
    }
  #endif
    return pgm_read_byte( &fontdata[font].height ) * textsize;
  }
  
  int16_t TFT_eSPI::fontHeight(void)
  {
    return fontHeight(textfont);
  }
  
  /***************************************************************************************
  ** Function name:           drawChar
  ** Description:             draw a single character in the Adafruit GLCD font
  ***************************************************************************************/
  void TFT_eSPI::drawChar(int32_t x, int32_t y, uint16_t c, uint32_t color, uint32_t bg, uint8_t size)
  {
    
      if ((x >= _width)            || // Clip right
        (y >= _height)           || // Clip bottom
        ((x + 6 * size - 1) < 0) || // Clip left
        ((y + 8 * size - 1) < 0))   // Clip top
      return;
  
    if (c < 32) return;
  #ifdef LOAD_GLCD
  //>>>>>>>>>>>>>>>>>>
  #ifdef LOAD_GFXFF
    if(!gfxFont) { // 'Classic' built-in font
  #endif
  //>>>>>>>>>>>>>>>>>>
  
    boolean fillbg = (bg != color);
    
    if ((size==1) && fillbg)
    {
      uint8_t column[6];
      uint8_t mask = 0x1;
      spi_begin();
  
      setWindow(x, y, x+5, y+8);
  
      for (int8_t i = 0; i < 5; i++ ) column[i] = pgm_read_byte(font + (c * 5) + i);
      column[5] = 0;
  
  #if defined (ESP8266) && !defined (ILI9488_DRIVER)
      color = (color >> 8) | (color << 8);
      bg = (bg >> 8) | (bg << 8);
  
      for (int8_t j = 0; j < 8; j++) {
        for (int8_t k = 0; k < 5; k++ ) {
          if (column[k] & mask) {
            SPI1W0 = color;
          }
          else {
            SPI1W0 = bg;
          }
          SPI1CMD |= SPIBUSY;
          while(SPI1CMD & SPIBUSY) {}
        }
  
        mask <<= 1;
  
        SPI1W0 = bg;
        SPI1CMD |= SPIBUSY;
        while(SPI1CMD & SPIBUSY) {}
      }
  #else // for ESP32 or ILI9488
  
      for (int8_t j = 0; j < 8; j++) {
        for (int8_t k = 0; k < 5; k++ ) {
          if (column[k] & mask) {tft_Write_16(color);}
          else {tft_Write_16(bg);}
        }
        mask <<= 1;
        tft_Write_16(bg);
      }
  
  #endif
  
      spi_end();
    }
    else
    {
      //spi_begin();          // Sprite class can use this function, avoiding spi_begin()
      inTransaction = true;
      for (int8_t i = 0; i < 6; i++ ) {
        uint8_t line;
        if (i == 5)
          line = 0x0;
        else
          line = pgm_read_byte(font + (c * 5) + i);
  
        if (size == 1) // default size
        {
          for (int8_t j = 0; j < 8; j++) {
            if (line & 0x1) drawPixel(x + i, y + j, color);
            line >>= 1;
          }
        }
        else {  // big size
          for (int8_t j = 0; j < 8; j++) {
            if (line & 0x1) fillRect(x + (i * size), y + (j * size), size, size, color);
            else if (fillbg) fillRect(x + i * size, y + j * size, size, size, bg);
            line >>= 1;
          }
        }
      }
      inTransaction = false;
      spi_end();              // Does nothing if Sprite class uses this function
    }
  
  //>>>>>>>>>>>>>>>>>>>>>>>>>>>
  #ifdef LOAD_GFXFF
    } else { // Custom font
  #endif
  //>>>>>>>>>>>>>>>>>>>>>>>>>>>
  #endif // LOAD_GLCD
  
  #ifdef LOAD_GFXFF
      // Filter out bad characters not present in font
      bool is_in_block_flag = false;
      #ifdef USE_M5_FONT_CREATOR
      int32_t index = -1;
      index = getUnicodeFontIndex(c);
      if (index != -1) {
        c = index;
        is_in_block_flag = true;
      }
      #else
      if ((c >= pgm_read_word(&gfxFont->first)) && (c <= pgm_read_word(&gfxFont->last )))
      {
        is_in_block_flag = true;
        c -= pgm_read_word(&gfxFont->first);
      }
      #endif
      
  
      if (is_in_block_flag == true)
      {
        //spi_begin();          // Sprite class can use this function, avoiding spi_begin()
        inTransaction = true;
  //>>>>>>>>>>>>>>>>>>>>>>>>>>>
  
        GFXglyph *glyph  = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c]);
        uint8_t  *bitmap = (uint8_t *)pgm_read_dword(&gfxFont->bitmap);
        
        uint32_t bo = pgm_read_dword(&glyph->bitmapOffset);
        uint8_t  w  = pgm_read_byte(&glyph->width),
                 h  = pgm_read_byte(&glyph->height);
                 //xa = pgm_read_byte(&glyph->xAdvance);
        int8_t   xo = pgm_read_byte(&glyph->xOffset),
                 yo = pgm_read_byte(&glyph->yOffset);
        uint8_t  xx, yy, bits=0, bit=0;
        int16_t  xo16 = 0, yo16 = 0;
          
        if(size > 1) {
          xo16 = xo;
          yo16 = yo;
        }
  
        if(_smooth_bpp == 2)
        {
          // const uint8_t alphamap[4] = {255, 191, 63, 0};
          const uint8_t alphamap[4] = {0, 63, 191, 255};
          uint8_t c;
          uint16_t fg = textcolor;
          uint16_t bg = textbgcolor;
          bit = 0;
          for(yy=0; yy<h; yy++) {
          for(xx=0; xx<w; xx++) {
            if(!(bit++ & 3)) {
              bits = pgm_read_byte(&bitmap[bo++]);
            }
            c = bits >> 6;
            if(c) {
              if (getColor) bg = getColor(x+xo+xx, y+yo+yy);
              if(size == 1) {
                drawPixel(x+xo+xx, y+yo+yy, alphaBlend(alphamap[c], fg, bg));
              } else {
                fillRect(x+(xo16+xx)*size, y+(yo16+yy)*size, size, size, alphaBlend(alphamap[c], fg, bg));
              }
            }
            bits <<= 2;
          }
        }
        inTransaction = false;
        spi_end();              // Does nothing if Sprite class uses this function
        return;
        }
  
  // Here we have 3 versions of the same function just for evaluation purposes
  // Comment out the next two #defines to revert to the slower Adafruit implementation
  
  // If FAST_LINE is defined then the free fonts are rendered using horizontal lines
  // this makes rendering fonts 2-5 times faster. Particularly good for large fonts.
  // This is an elegant solution since it still uses generic functions present in the
  // stock library.
  
  // If FAST_SHIFT is defined then a slightly faster (at least for AVR processors)
  // shifting bit mask is used
  
  // Free fonts don't look good when the size multiplier is >1 so we could remove
  // code if this is not wanted and speed things up
  
  #define FAST_HLINE
  #define FAST_SHIFT
  //FIXED_SIZE is an option in User_Setup.h that only works with FAST_LINE enabled
  
  #ifdef FIXED_SIZE
        x+=xo; // Save 88 bytes of FLASH
        y+=yo;
  #endif
  
  #ifdef FAST_HLINE
      #ifdef FAST_SHIFT
        uint16_t hpc = 0; // Horizontal foreground pixel count
        for(yy=0; yy<h; yy++) {
          for(xx=0; xx<w; xx++) {
            if(bit == 0) {
              bits = pgm_read_byte(&bitmap[bo++]);
                          bit  = 0x80;
            }
            if(bits & bit) hpc++;
            else {
             if (hpc) {
  #ifndef FIXED_SIZE
                if(size == 1) drawFastHLine(x+xo+xx-hpc, y+yo+yy, hpc, color);
                else fillRect(x+(xo16+xx-hpc)*size, y+(yo16+yy)*size, size*hpc, size, color);
  #else
                drawFastHLine(x+xx-hpc, y+yy, hpc, color);
  #endif
                hpc=0;
              }
            }
            bit >>= 1;
          }
        // Draw pixels for this line as we are about to increment yy
          if (hpc) {
  #ifndef FIXED_SIZE
            if(size == 1) drawFastHLine(x+xo+xx-hpc, y+yo+yy, hpc, color);
            else fillRect(x+(xo16+xx-hpc)*size, y+(yo16+yy)*size, size*hpc, size, color);
  #else
            drawFastHLine(x+xx-hpc, y+yy, hpc, color);
  #endif
            hpc=0;
          }
        }
    #else
        uint16_t hpc = 0; // Horizontal foreground pixel count
        for(yy=0; yy<h; yy++) {
          for(xx=0; xx<w; xx++) {
            if(!(bit++ & 7)) {
              bits = pgm_read_byte(&bitmap[bo++]);
            }
            if(bits & 0x80) hpc++;
            else {
              if (hpc) {
                if(size == 1) drawFastHLine(x+xo+xx-hpc, y+yo+yy, hpc, color);
                else fillRect(x+(xo16+xx-hpc)*size, y+(yo16+yy)*size, size*hpc, size, color);
                hpc=0;
              }
            }
            bits <<= 1;
          }
          // Draw pixels for this line as we are about to increment yy
          if (hpc) {
            if(size == 1) drawFastHLine(x+xo+xx-hpc, y+yo+yy, hpc, color);
            else fillRect(x+(xo16+xx-hpc)*size, y+(yo16+yy)*size, size*hpc, size, color);
            hpc=0;
          }
        }
    #endif
  
  #else
        for(yy=0; yy<h; yy++) {
          for(xx=0; xx<w; xx++) {
            if(!(bit++ & 7)) {
              bits = pgm_read_byte(&bitmap[bo++]);
            }
            if(bits & 0x80) {
              if(size == 1) {
                drawPixel(x+xo+xx, y+yo+yy, color);
              } else {
                fillRect(x+(xo16+xx)*size, y+(yo16+yy)*size, size, size, color);
              }
            }
            bits <<= 1;
          }
        }
  #endif
        inTransaction = false;
        spi_end();              // Does nothing if Sprite class uses this function
      }
  #endif
  
  #ifdef LOAD_GLCD
    #ifdef LOAD_GFXFF
    } // End classic vs custom font
    #endif
  #endif
  
  }
  
  
  /***************************************************************************************
  ** Function name:           setAddrWindow
  ** Description:             define an area to receive a stream of pixels
  ***************************************************************************************/
  // Chip select is high at the end of this function
  void TFT_eSPI::setAddrWindow(int32_t x0, int32_t y0, int32_t w, int32_t h)
  {
    spi_begin();
  
    setWindow(x0, y0, x0 + w - 1, y0 + h - 1);
  
    spi_end();
  }
  
  
  /***************************************************************************************
  ** Function name:           setWindow
  ** Description:             define an area to receive a stream of pixels
  ***************************************************************************************/
  // Chip select stays low, call spi_begin first. Use setAddrWindow() from sketches
  #if defined (ESP8266) && !defined (RPI_WRITE_STROBE) && !defined (RPI_ILI9486_DRIVER)
  void TFT_eSPI::setWindow(int32_t xs, int32_t ys, int32_t xe, int32_t ye)
  {
    //spi_begin(); // Must be called before setWimdow
  
  #ifdef CGRAM_OFFSET
    xs+=colstart;
    xe+=colstart;
    ys+=rowstart;
    ye+=rowstart;
  #endif
  
    // Column addr set
    DC_C;
  
    SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);
  
    SPI1W0 = TFT_CASET;
    SPI1CMD |= SPIBUSY;
  
    addr_col = 0xFFFF;
    addr_row = 0xFFFF;
    
    while(SPI1CMD & SPIBUSY) {}
  
    DC_D;
  
    SPI1U1 = (31 << SPILMOSI) | (31 << SPILMISO);
    // Load the two coords as a 32 bit value and shift in one go
    SPI1W0 = (xs >> 8) | (uint16_t)(xs << 8) | ((uint8_t)(xe >> 8)<<16 | (xe << 24));
    SPI1CMD |= SPIBUSY;
    while(SPI1CMD & SPIBUSY) {}
  
    // Row addr set
    DC_C;
  
    SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);
  
    SPI1W0 = TFT_PASET;
    SPI1CMD |= SPIBUSY;
    while(SPI1CMD & SPIBUSY) {}
  
    DC_D;
  
    SPI1U1 = (31 << SPILMOSI) | (31 << SPILMISO);
    // Load the two coords as a 32 bit value and shift in one go
    SPI1W0 = (ys >> 8) | (uint16_t)(ys << 8) | ((uint8_t)(ye >> 8)<<16 | (ye << 24));
    SPI1CMD |= SPIBUSY;
    while(SPI1CMD & SPIBUSY) {}
  
    // write to RAM
    DC_C;
  
    SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);
    SPI1W0 = TFT_RAMWR;
    SPI1CMD |= SPIBUSY;
    while(SPI1CMD & SPIBUSY) {}
  
    DC_D;
  
    SPI1U1 = (15 << SPILMOSI) | (15 << SPILMISO);
    //spi_end();
  }
  
  #elif defined (ESP8266) && !defined (RPI_WRITE_STROBE) && defined (RPI_ILI9486_DRIVER) // This is for the RPi display that needs 16 bits
  
  void TFT_eSPI::setWindow(int32_t xs, int32_t ys, int32_t xe, int32_t ye)
  {
    //spi_begin(); // Must be called before setWimdow
  
    addr_col = 0xFFFF;
    addr_row = 0xFFFF;
    
    // Column addr set
    DC_C;
  
    SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);
  
    SPI1W0 = TFT_CASET<<8;
    SPI1CMD |= SPIBUSY;
    while(SPI1CMD & SPIBUSY) {}
  
    DC_D;
  
    uint8_t xb[] = { 0, (uint8_t) (xs>>8), 0, (uint8_t) (xs>>0), 0, (uint8_t) (xe>>8), 0, (uint8_t) (xe>>0), };
    spi.writePattern(&xb[0], 8, 1);
  
    // Row addr set
    DC_C;
  
    SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);
  
    SPI1W0 = TFT_PASET<<8;
    SPI1CMD |= SPIBUSY;
    while(SPI1CMD & SPIBUSY) {}
  
    DC_D;
  
    uint8_t yb[] = { 0, (uint8_t) (ys>>8), 0, (uint8_t) (ys>>0), 0, (uint8_t) (ye>>8), 0, (uint8_t) (ye>>0), };
    spi.writePattern(&yb[0], 8, 1);
  
    // write to RAM
    DC_C;
  
    SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);
    SPI1W0 = TFT_RAMWR<<8;
    SPI1CMD |= SPIBUSY;
    while(SPI1CMD & SPIBUSY) {}
  
    DC_D;
  
    // Re-instate SPI flags settings corrupted by SPI library writePattern() call
    SPI1U = SPI1U_WRITE;
  
    //spi_end();
  }
  
  #else
  
  #if defined (ESP8266) && defined (RPI_ILI9486_DRIVER) // This is for the RPi display that needs 16 bits
  void TFT_eSPI::setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1)
  {
    //spi_begin(); // Must be called before setWimdow
  
    SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);
  
    // Column addr set
    DC_C;
  
    SPI1W0 = TFT_CASET<<(CMD_BITS + 1 - 8);
    SPI1CMD |= SPIBUSY;
    addr_col = 0xFFFF; // Use the waiting time to do something useful
    addr_row = 0xFFFF;
    while(SPI1CMD & SPIBUSY) {}
    DC_D;
  
    SPI1W0 = x0 >> 0;
    SPI1CMD |= SPIBUSY;
    x0 = x0 << 8; // Use the waiting time to do something useful
    while(SPI1CMD & SPIBUSY) {}
  
    SPI1W0 = x0;
    SPI1CMD |= SPIBUSY;
    while(SPI1CMD & SPIBUSY) {}
  
    SPI1W0 = x1 >> 0;
    SPI1CMD |= SPIBUSY;
    x1 = x1 << 8; // Use the waiting time to do something useful
    while(SPI1CMD & SPIBUSY) {}
  
    SPI1W0 = x1;
    SPI1CMD |= SPIBUSY;
    while(SPI1CMD & SPIBUSY) {}
    
    // Row addr set
    DC_C;
  
    SPI1W0 = TFT_PASET<<(CMD_BITS + 1 - 8);
    SPI1CMD |= SPIBUSY;
    while(SPI1CMD & SPIBUSY) {}
    DC_D;
  
    SPI1W0 = y0 >> 0;
    SPI1CMD |= SPIBUSY;
    y0 = y0 << 8; // Use the waiting time to do something useful
    while(SPI1CMD & SPIBUSY) {}
  
    SPI1W0 = y0;
    SPI1CMD |= SPIBUSY;
    while(SPI1CMD & SPIBUSY) {}
  
    SPI1W0 = y1 >> 0;
    SPI1CMD |= SPIBUSY;
    y1 = y1 << 8; // Use the waiting time to do something useful
    while(SPI1CMD & SPIBUSY) {}
  
    SPI1W0 = y1;
    SPI1CMD |= SPIBUSY;
    while(SPI1CMD & SPIBUSY) {}
    
    // write to RAM
    DC_C;
  
    SPI1W0 = TFT_RAMWR<<(CMD_BITS + 1 - 8);
    SPI1CMD |= SPIBUSY;
    while(SPI1CMD & SPIBUSY) {}
  
    DC_D;
  
    //spi_end();
  }
  
  #else // This is for the ESP32
  
  void TFT_eSPI::setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1)
  {
    //spi_begin(); // Must be called before setWimdow
  
    addr_col = 0xFFFF;
    addr_row = 0xFFFF;
  
  #ifdef CGRAM_OFFSET
    x0+=colstart;
    x1+=colstart;
    y0+=rowstart;
    y1+=rowstart;
  #endif
  
    DC_C;
  
    tft_Write_8(TFT_CASET);
  
    DC_D;
  
  #if defined (RPI_ILI9486_DRIVER)
    uint8_t xb[] = { 0, (uint8_t) (x0>>8), 0, (uint8_t) (x0>>0), 0, (uint8_t) (x1>>8), 0, (uint8_t) (x1>>0), };
    spi.writePattern(&xb[0], 8, 1);
  #else
    tft_Write_32(SPI_32(x0, x1));
  #endif
  
    DC_C;
  
    // Row addr set
    tft_Write_8(TFT_PASET);
  
    DC_D;
  
  #if defined (RPI_ILI9486_DRIVER)
    uint8_t yb[] = { 0, (uint8_t) (y0>>8), 0, (uint8_t) (y0>>0), 0, (uint8_t) (y1>>8), 0, (uint8_t) (y1>>0), };
    spi.writePattern(&yb[0], 8, 1);
  #else
    tft_Write_32(SPI_32(y0, y1));
  #endif
  
    DC_C;
  
    // write to RAM
    tft_Write_8(TFT_RAMWR);
  
    DC_D;
  
    //spi_end();
  }
  #endif // end RPI_ILI9486_DRIVER check
  #endif // end ESP32 check
  
  
  /***************************************************************************************
  ** Function name:           readAddrWindow
  ** Description:             define an area to read a stream of pixels
  ***************************************************************************************/
  // Chip select stays low
  #if defined (ESP8266) && !defined (RPI_WRITE_STROBE)
  void TFT_eSPI::readAddrWindow(int32_t xs, int32_t ys, int32_t w, int32_t h)
  {
  
    int32_t xe = xs + w - 1;
    int32_t ye = ys + h - 1;
  
    addr_col = 0xFFFF;
    addr_row = 0xFFFF;
    
  #ifdef CGRAM_OFFSET
    xs += colstart;
    xe += colstart;
    ys += rowstart;
    ye += rowstart;
  #endif
  
    // Column addr set
    DC_C;
  
    SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);
  
    SPI1W0 = TFT_CASET;
    SPI1CMD |= SPIBUSY;
    while(SPI1CMD & SPIBUSY) {}
  
    DC_D;
  
    SPI1U1 = (31 << SPILMOSI) | (31 << SPILMISO);
    // Load the two coords as a 32 bit value and shift in one go
    SPI1W0 = (xs >> 8) | (uint16_t)(xs << 8) | ((uint8_t)(xe >> 8)<<16 | (xe << 24));
    SPI1CMD |= SPIBUSY;
    while(SPI1CMD & SPIBUSY) {}
  
    // Row addr set
    DC_C;
  
    SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);
  
    SPI1W0 = TFT_PASET;
    SPI1CMD |= SPIBUSY;
    while(SPI1CMD & SPIBUSY) {}
  
    DC_D;
  
    SPI1U1 = (31 << SPILMOSI) | (31 << SPILMISO);
    // Load the two coords as a 32 bit value and shift in one go
    SPI1W0 = (ys >> 8) | (uint16_t)(ys << 8) | ((uint8_t)(ye >> 8)<<16 | (ye << 24));
    SPI1CMD |= SPIBUSY;
    while(SPI1CMD & SPIBUSY) {}
  
    // read from RAM
    DC_C;
  
    SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);
    SPI1W0 = TFT_RAMRD;
    SPI1CMD |= SPIBUSY;
    while(SPI1CMD & SPIBUSY) {}
  
    DC_D;
  
  }
  
  #else //ESP32
  
  void TFT_eSPI::readAddrWindow(int32_t xs, int32_t ys, int32_t w, int32_t h)
  {
  
    int32_t xe = xs + w - 1;
    int32_t ye = ys + h - 1;
  
    addr_col = 0xFFFF;
    addr_row = 0xFFFF;
  
  #ifdef CGRAM_OFFSET
  xs += colstart;
  xe += colstart;
  ys += rowstart;
  ye += rowstart;
  #endif
  
    // Column addr set
    DC_C;
  
    tft_Write_8(TFT_CASET);
  
    DC_D;
  
    tft_Write_32(SPI_32(xs, xe));
  
    // Row addr set
    DC_C;
  
    tft_Write_8(TFT_PASET);
  
    DC_D;
  
    tft_Write_32(SPI_32(ys, ye));
    
    DC_C;
  
    tft_Write_8(TFT_RAMRD); // Read CGRAM command
  
    DC_D;
  
  }
  
  #endif
  
  /***************************************************************************************
  ** Function name:           drawPixel
  ** Description:             push a single pixel at an arbitrary position
  ***************************************************************************************/
  #if defined (ESP8266) && !defined (RPI_WRITE_STROBE)
  void TFT_eSPI::drawPixel(int32_t x, int32_t y, uint32_t color)
  {
    // Range checking
    if ((x < 0) || (y < 0) ||(x >= _width) || (y >= _height)) return;
  
  #ifdef CGRAM_OFFSET
    x+=colstart;
    y+=rowstart;
  #endif
  
    spi_begin();
  
    // No need to send x if it has not changed (speeds things up)
    if (addr_col != x) {
  
      DC_C;
  
      SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);
      SPI1W0 = TFT_CASET<<(CMD_BITS + 1 - 8);
      SPI1CMD |= SPIBUSY;
      while(SPI1CMD & SPIBUSY) {}
  
      DC_D;
  
  #if defined (RPI_ILI9486_DRIVER) // This is for the RPi display that needs 16 bits per byte
      uint8_t cBin[] = { 0, (uint8_t) (x>>8), 0, (uint8_t) (x>>0)};
      spi.writePattern(&cBin[0], 4, 2);
  #else
      SPI1U1 = (31 << SPILMOSI) | (31 << SPILMISO);
      // Load the two coords as a 32 bit value and shift in one go
      uint32_t xswap = (x >> 8) | (uint16_t)(x << 8);
      SPI1W0 = xswap | (xswap << 16);
      SPI1CMD |= SPIBUSY;
      while(SPI1CMD & SPIBUSY) {}
  #endif
  
      addr_col = x;
    }
  
    // No need to send y if it has not changed (speeds things up)
    if (addr_row != y) {
  
      DC_C;
  
      SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);
  
      SPI1W0 = TFT_PASET<<(CMD_BITS + 1 - 8);
      SPI1CMD |= SPIBUSY;
      while(SPI1CMD & SPIBUSY) {}
  
      DC_D;
  
  #if defined (RPI_ILI9486_DRIVER) // This is for the RPi display that needs 16 bits per byte
      uint8_t cBin[] = { 0, (uint8_t) (y>>8), 0, (uint8_t) (y>>0)};
      spi.writePattern(&cBin[0], 4, 2);
  #else
      SPI1U1 = (31 << SPILMOSI) | (31 << SPILMISO);
      // Load the two coords as a 32 bit value and shift in one go
      uint32_t yswap = (y >> 8) | (uint16_t)(y << 8);
      SPI1W0 = yswap | (yswap << 16);
      SPI1CMD |= SPIBUSY;
      while(SPI1CMD & SPIBUSY) {}
  #endif
  
      addr_row = y;
    }
  
    DC_C;
  
    SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);
  
    SPI1W0 = TFT_RAMWR<<(CMD_BITS + 1 - 8);
    SPI1CMD |= SPIBUSY;
    while(SPI1CMD & SPIBUSY) {}
  
    DC_D;
  
  #if  defined (ILI9488_DRIVER)
    tft_Write_16(color);
  #else
    SPI1U1 = (15 << SPILMOSI) | (15 << SPILMISO);
  
    SPI1W0 = (color >> 8) | (color << 8);
    SPI1CMD |= SPIBUSY;
    while(SPI1CMD & SPIBUSY) {}
  #endif
  
    spi_end();
  }
  
  #else
  
  #if defined (ESP8266) && defined (RPI_ILI9486_DRIVER) // This is for the RPi display that needs 16 bits
  
  void TFT_eSPI::drawPixel(int32_t x, int32_t y, uint32_t color)
  {
    // Range checking
    if ((x < 0) || (y < 0) ||(x >= _width) || (y >= _height)) return;
  
    spi_begin();
  
    SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);
    // No need to send x if it has not changed (speeds things up)
    if (addr_col != x) {
      DC_C;
  
      SPI1W0 = TFT_CASET<<(CMD_BITS + 1 - 8);
      SPI1CMD |= SPIBUSY;
      while(SPI1CMD & SPIBUSY) {}
      DC_D;
  
      SPI1W0 = x >> 0;
      SPI1CMD |= SPIBUSY;
      while(SPI1CMD & SPIBUSY) {}
  
      SPI1W0 = x << 8;
      SPI1CMD |= SPIBUSY;
      while(SPI1CMD & SPIBUSY) {}
  
      SPI1W0 = x >> 0;
      SPI1CMD |= SPIBUSY;
      while(SPI1CMD & SPIBUSY) {}
  
      SPI1W0 = x << 8;
      SPI1CMD |= SPIBUSY;
      while(SPI1CMD & SPIBUSY) {}
      
      addr_col = x;
    }
  
    // No need to send y if it has not changed (speeds things up)
    if (addr_row != y) {
      DC_C;
  
      SPI1W0 = TFT_PASET<<(CMD_BITS + 1 - 8);
      SPI1CMD |= SPIBUSY;
      while(SPI1CMD & SPIBUSY) {}
      DC_D;
  
      SPI1W0 = y >> 0;
      SPI1CMD |= SPIBUSY;
      while(SPI1CMD & SPIBUSY) {}
  
      SPI1W0 = y << 8;
      SPI1CMD |= SPIBUSY;
      while(SPI1CMD & SPIBUSY) {}
  
      SPI1W0 = y >> 0;
      SPI1CMD |= SPIBUSY;
      while(SPI1CMD & SPIBUSY) {}
  
      SPI1W0 = y << 8;
      SPI1CMD |= SPIBUSY;
      while(SPI1CMD & SPIBUSY) {}
      
      addr_row = y;
    }
  
    DC_C;
  
    SPI1W0 = TFT_RAMWR<<(CMD_BITS + 1 - 8);
    SPI1CMD |= SPIBUSY;
    while(SPI1CMD & SPIBUSY) {}
  
    DC_D;
  
    SPI1W0 = (color >> 8) | (color << 8);
    SPI1CMD |= SPIBUSY;
    while(SPI1CMD & SPIBUSY) {}
  
    spi_end();
  }
  
  #else // ESP32
  
  void TFT_eSPI::drawPixel(int32_t x, int32_t y, uint32_t color)
  {
    // Range checking
    if ((x < 0) || (y < 0) ||(x >= _width) || (y >= _height)) return;
  
    spi_begin();
  
  #ifdef CGRAM_OFFSET
    x+=colstart;
    y+=rowstart;
  #endif
  
    DC_C;
  
    // No need to send x if it has not changed (speeds things up)
    if (addr_col != x) {
  
      tft_Write_8(TFT_CASET);
  
      DC_D;
  
  #if defined (RPI_ILI9486_DRIVER)
      uint8_t xb[] = { 0, (uint8_t) (x>>8), 0, (uint8_t) (x>>0), 0, (uint8_t) (x>>8), 0, (uint8_t) (x>>0), };
      spi.writePattern(&xb[0], 8, 1);
  #else
      tft_Write_32(SPI_32(x, x));
  #endif
  
      DC_C;
  
      addr_col = x;
    }
  
    // No need to send y if it has not changed (speeds things up)
    if (addr_row != y) {
  
      tft_Write_8(TFT_PASET);
  
      DC_D;
  
  #if defined (RPI_ILI9486_DRIVER)
      uint8_t yb[] = { 0, (uint8_t) (y>>8), 0, (uint8_t) (y>>0), 0, (uint8_t) (y>>8), 0, (uint8_t) (y>>0), };
      spi.writePattern(&yb[0], 8, 1);
  #else
      tft_Write_32(SPI_32(y, y));
  #endif
  
      DC_C;
  
      addr_row = y;
    }
  
  
    tft_Write_8(TFT_RAMWR);
  
    DC_D;
  
    tft_Write_16(color);
  
    spi_end();
  }
  #endif
  #endif
  
  
  /***************************************************************************************
  ** Function name:           pushColor
  ** Description:             push a single pixel
  ***************************************************************************************/
  void TFT_eSPI::pushColor(uint16_t color)
  {
    spi_begin();
  
    tft_Write_16(color);
  
    spi_end();
  }
  
  
  /***************************************************************************************
  ** Function name:           pushColor
  ** Description:             push a single colour to "len" pixels
  ***************************************************************************************/
  void TFT_eSPI::pushColor(uint16_t color, uint32_t len)
  {
    spi_begin();
  
  #ifdef RPI_WRITE_STROBE
    uint8_t colorBin[] = { (uint8_t) (color >> 8), (uint8_t) color };
    if(len) spi.writePattern(&colorBin[0], 2, 1); len--;
    while(len--) {WR_L; WR_H;}
  #else
    #if defined (ESP32_PARALLEL)
      while (len--) {tft_Write_16(color);}
    #else
      writeBlock(color, len);
    #endif
  #endif
  
    spi_end();
  }
  
  /***************************************************************************************
  ** Function name:           startWrite
  ** Description:             begin transaction with CS low, MUST later call endWrite
  ***************************************************************************************/
  void TFT_eSPI::startWrite(void)
  {
    spi_begin();
    inTransaction = true;
  }
  
  /***************************************************************************************
  ** Function name:           endWrite
  ** Description:             end transaction with CS high
  ***************************************************************************************/
  void TFT_eSPI::endWrite(void)
  {
    inTransaction = false;
    spi_end();
  }
  
  /***************************************************************************************
  ** Function name:           writeColor (use startWrite() and endWrite() before & after)
  ** Description:             raw write of "len" pixels avoiding transaction check
  ***************************************************************************************/
  void TFT_eSPI::writeColor(uint16_t color, uint32_t len)
  {
  #ifdef RPI_WRITE_STROBE
    uint8_t colorBin[] = { (uint8_t) (color >> 8), (uint8_t) color };
    if(len) spi.writePattern(&colorBin[0], 2, 1); len--;
    while(len--) {WR_L; WR_H;}
  #else
    #if defined (ESP32_PARALLEL)
      while (len--) {tft_Write_16(color);}
    #else
      writeBlock(color, len);
    #endif
  #endif
  }
  
  /***************************************************************************************
  ** Function name:           pushColors
  ** Description:             push an array of pixels for 16 bit raw image drawing
  ***************************************************************************************/
  // Assumed that setAddrWindow() has previously been called
  
  void TFT_eSPI::pushColors(uint8_t *data, uint32_t len)
  {
    spi_begin();
  
  #if defined (RPI_WRITE_STROBE)
    while ( len >=64 ) {spi.writePattern(data, 64, 1); data += 64; len -= 64; }
    if (len) spi.writePattern(data, len, 1);
  #else
    #ifdef ESP32_PARALLEL
      while (len--) {tft_Write_8(*data); data++;}
    #elif  defined (ILI9488_DRIVER)
      uint16_t color;
      while (len>1) {color = (*data++); color |= ((*data++)<<8); tft_Write_16(color); len-=2;}
    #else
      #if (SPI_FREQUENCY == 80000000)
        while ( len >=64 ) {spi.writePattern(data, 64, 1); data += 64; len -= 64; }
        if (len) spi.writePattern(data, len, 1);
      #else
        spi.writeBytes(data, len);
      #endif
    #endif
  #endif
  
    spi_end();
  }
  
  
  /***************************************************************************************
  ** Function name:           pushColors
  ** Description:             push an array of pixels, for image drawing
  ***************************************************************************************/
  void TFT_eSPI::pushColors(uint16_t *data, uint32_t len, bool swap)
  {
    spi_begin();
  
  #if defined (ESP32) || defined (ILI9488_DRIVER)
    #if defined (ESP32_PARALLEL) || defined (ILI9488_DRIVER)
      if (swap) while ( len-- ) {tft_Write_16(*data); data++;}
      else while ( len-- ) {tft_Write_16S(*data); data++;}
    #else
      if (swap) spi.writePixels(data,len<<1);
      else spi.writeBytes((uint8_t*)data,len<<1);
    #endif
  #else
  
    uint32_t color[8];
  
    SPI1U1 = (255 << SPILMOSI) | (255 << SPILMISO);
  
  
    while(len>15)
    {
  
      if (swap)
      {
        uint32_t i = 0;
        while(i<8)
        {
          color[i]  = (*data >> 8) | (uint16_t)(*data << 8);
          data++;
          color[i] |= ((*data >> 8) | (*data << 8)) << 16;
          data++;
          i++;
        }
      }
      else
      {
        memcpy(color,data,32);
        data+=16;
      }
  
      len -= 16;
  
      // ESP8266 wait time here at 40MHz SPI is ~5.45us
      while(SPI1CMD & SPIBUSY) {}
      SPI1W0 = color[0];
      SPI1W1 = color[1];
      SPI1W2 = color[2];
      SPI1W3 = color[3];
      SPI1W4 = color[4];
      SPI1W5 = color[5];
      SPI1W6 = color[6];
      SPI1W7 = color[7];
      SPI1CMD |= SPIBUSY;
    }
  
    if(len)
    {
      uint32_t bits = (len*16-1); // bits left to shift - 1
      if (swap)
      {
        uint16_t* ptr = (uint16_t*)color;
        while(len--)
        {
          *ptr++ = (*(data) >> 8) | (uint16_t)(*(data) << 8);
          data++;
        }
      }
      else
      {
        memcpy(color,data,len<<1);
      }
      while(SPI1CMD & SPIBUSY) {}
      SPI1U1 = (bits << SPILMOSI) | (bits << SPILMISO);
      SPI1W0 = color[0];
      SPI1W1 = color[1];
      SPI1W2 = color[2];
      SPI1W3 = color[3];
      SPI1W4 = color[4];
      SPI1W5 = color[5];
      SPI1W6 = color[6];
      SPI1W7 = color[7];
      SPI1CMD |= SPIBUSY;
    }
  
    while(SPI1CMD & SPIBUSY) {}
  
  #endif
  
    spi_end();
  }
  
  
  /***************************************************************************************
  ** Function name:           drawLine
  ** Description:             draw a line between 2 arbitrary points
  ***************************************************************************************/
  // Bresenham's algorithm - thx wikipedia - speed enhanced by Bodmer to use
  // an efficient FastH/V Line draw routine for line segments of 2 pixels or more
  
  #if defined (RPI_ILI9486_DRIVER) || defined (ESP32) || defined (RPI_WRITE_STROBE) || defined (HX8357D_DRIVER) || defined (ILI9488_DRIVER)
  
  void TFT_eSPI::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t color)
  {
    //spi_begin();          // Sprite class can use this function, avoiding spi_begin()
    inTransaction = true;
    boolean steep = abs(y1 - y0) > abs(x1 - x0);
    if (steep) {
      swap_coord(x0, y0);
      swap_coord(x1, y1);
    }
  
    if (x0 > x1) {
      swap_coord(x0, x1);
      swap_coord(y0, y1);
    }
  
    int32_t dx = x1 - x0, dy = abs(y1 - y0);;
  
    int32_t err = dx >> 1, ystep = -1, xs = x0, dlen = 0;
  
    if (y0 < y1) ystep = 1;
  
    // Split into steep and not steep for FastH/V separation
    if (steep) {
      for (; x0 <= x1; x0++) {
        dlen++;
        err -= dy;
        if (err < 0) {
          err += dx;
          if (dlen == 1) drawPixel(y0, xs, color);
          else drawFastVLine(y0, xs, dlen, color);
          dlen = 0; y0 += ystep; xs = x0 + 1;
        }
      }
      if (dlen) drawFastVLine(y0, xs, dlen, color);
    }
    else
    {
      for (; x0 <= x1; x0++) {
        dlen++;
        err -= dy;
        if (err < 0) {
          err += dx;
          if (dlen == 1) drawPixel(xs, y0, color);
          else drawFastHLine(xs, y0, dlen, color);
          dlen = 0; y0 += ystep; xs = x0 + 1;
        }
      }
      if (dlen) drawFastHLine(xs, y0, dlen, color);
    }
    inTransaction = false;
    spi_end();
  }
  
  #else
  
  // This is a weeny bit faster
  void TFT_eSPI::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t color)
  {
  
    boolean steep = abs(y1 - y0) > abs(x1 - x0);
  
    if (steep) {
      swap_coord(x0, y0);
      swap_coord(x1, y1);
    }
  
    if (x0 > x1) {
      swap_coord(x0, x1);
      swap_coord(y0, y1);
    }
  
    if (x1 < 0) return;
  
    int16_t dx, dy;
    dx = x1 - x0;
    dy = abs(y1 - y0);
  
    int16_t err = dx / 2;
    int8_t ystep = (y0 < y1) ? 1 : (-1);
  
    spi_begin();
  
    int16_t swapped_color = (color >> 8) | (color << 8);
  
    if (steep)  // y increments every iteration (y0 is x-axis, and x0 is y-axis)
    {
      if (x1 >= (int32_t)_height) x1 = _height - 1;
  
      for (; x0 <= x1; x0++) {
        if ((x0 >= 0) && (y0 >= 0) && (y0 < _width)) break;
        err -= dy;
        if (err < 0) {
          err += dx;
          y0 += ystep;
        }
      }
  
      if (x0 > x1) {spi_end(); return;}
  
      setWindow(y0, x0, y0, _height);
      SPI1W0 = swapped_color;
      for (; x0 <= x1; x0++) {
        while(SPI1CMD & SPIBUSY) {}
        SPI1CMD |= SPIBUSY;
  
        err -= dy;
        if (err < 0) {
          y0 += ystep;
          if ((y0 < 0) || (y0 >= _width)) break;
          err += dx;
          while(SPI1CMD & SPIBUSY) {}
          setWindow(y0, x0+1, y0, _height);
          SPI1W0 = swapped_color;
        }
      }
    }
    else    // x increments every iteration (x0 is x-axis, and y0 is y-axis)
    {
      if (x1 >= _width) x1 = _width - 1;
  
      for (; x0 <= x1; x0++) {
        if ((x0 >= 0) && (y0 >= 0) && (y0 < (int32_t)_height)) break;
        err -= dy;
        if (err < 0) {
            err += dx;
            y0 += ystep;
        }
      }
  
      if (x0 > x1) {spi_end(); return;}
  
      setWindow(x0, y0, _width, y0);
      SPI1W0 = swapped_color;
      for (; x0 <= x1; x0++) {
        while(SPI1CMD & SPIBUSY) {}
        SPI1CMD |= SPIBUSY;
  
        err -= dy;
        if (err < 0) {
          y0 += ystep;
          if ((y0 < 0) || (y0 >= (int32_t)_height)) break;
          err += dx;
          while(SPI1CMD & SPIBUSY) {}
          setWindow(x0+1, y0, _width, y0);
          SPI1W0 = swapped_color;
        }
      }
    }
  
    while(SPI1CMD & SPIBUSY) {}
  
    spi_end();
  }
  
  #endif
  
  
  /***************************************************************************************
  ** Function name:           drawFastVLine
  ** Description:             draw a vertical line
  ***************************************************************************************/
  #if defined (ESP8266) && !defined (RPI_WRITE_STROBE)
  void TFT_eSPI::drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color)
  {
    // Clipping
    if ((x < 0) || (x >= _width) || (y >= _height)) return;
  
    if (y < 0) { h += y; y = 0; }
  
    if ((y + h) > _height) h = _height - y;
  
    if (h < 1) return;
  
    spi_begin();
  
    setWindow(x, y, x, y + h - 1);
  
    writeBlock(color, h);
    
    spi_end();
  }
  
  #else
  
  void TFT_eSPI::drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color)
  {
    // Clipping
    if ((x < 0) || (x >= _width) || (y >= _height)) return;
  
    if (y < 0) { h += y; y = 0; }
  
    if ((y + h) > _height) h = _height - y;
  
    if (h < 1) return;
  
    spi_begin();
  
    setWindow(x, y, x, y + h - 1);
      
  #ifdef RPI_WRITE_STROBE
    #if defined (ESP8266)
      SPI1W0 = (color >> 8) | (color << 8);
      SPI1CMD |= SPIBUSY;
      while(SPI1CMD & SPIBUSY) {}
    #else
      tft_Write_16(color);
    #endif
      h--;
      while(h--) {WR_L; WR_H;}
  #else
    #ifdef ESP32_PARALLEL
      while (h--) {tft_Write_16(color);}
    #else
      writeBlock(color, h);
    #endif
  #endif
  
    spi_end();
  }
  #endif
  
  /***************************************************************************************
  ** Function name:           drawFastHLine
  ** Description:             draw a horizontal line
  ***************************************************************************************/
  #if defined (ESP8266) && !defined (RPI_WRITE_STROBE)
  void TFT_eSPI::drawFastHLine(int32_t x, int32_t y, int32_t w, uint32_t color)
  {
    // Clipping
    if ((y < 0) || (x >= _width) || (y >= _height)) return;
  
    if (x < 0) { w += x; x = 0; }
  
    if ((x + w) > _width)  w = _width  - x;
  
    if (w < 1) return;
  
    spi_begin();
  
    setWindow(x, y, x + w - 1, y);
  
    writeBlock(color, w);
    
    spi_end();
  }
  
  #else
  
  void TFT_eSPI::drawFastHLine(int32_t x, int32_t y, int32_t w, uint32_t color)
  {
    // Clipping
    if ((y < 0) || (x >= _width) || (y >= _height)) return;
  
    if (x < 0) { w += x; x = 0; }
  
    if ((x + w) > _width)  w = _width  - x;
  
    if (w < 1) return;
  
    spi_begin();
  
    setWindow(x, y, x + w - 1, y);
  
  #ifdef RPI_WRITE_STROBE
    #if defined (ESP8266)
      SPI1W0 = (color >> 8) | (color << 8);
      SPI1CMD |= SPIBUSY;
      while(SPI1CMD & SPIBUSY) {}
    #else
      tft_Write_16(color);
    #endif
      w--;
      while(w--) {WR_L; WR_H;}
  #else
    #ifdef ESP32_PARALLEL
      while (w--) {tft_Write_16(color);}
    #else
      writeBlock(color, w);
    #endif
  #endif
  
    spi_end();
  }
  #endif
  
  /***************************************************************************************
  ** Function name:           fillRect
  ** Description:             draw a filled rectangle
  ***************************************************************************************/
  #if defined (ESP8266) && !defined (RPI_WRITE_STROBE)
  void TFT_eSPI::fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color)
  {
    // Clipping
    if ((x >= _width) || (y >= _height)) return;
    
    if (x < 0) { w += x; x = 0; }
    if (y < 0) { h += y; y = 0; }
  
    if ((x + w) > _width)  w = _width  - x;
    if ((y + h) > _height) h = _height - y;
  
    if ((w < 1) || (h < 1)) return;
  
    spi_begin();
  
    setWindow(x, y, x + w - 1, y + h - 1);
  
    writeBlock(color, w * h);
    
    spi_end();
  }
  
  #else
  
  void TFT_eSPI::fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color)
  {
  
    // Clipping
    if ((x >= _width) || (y >= _height)) return;
    
    if (x < 0) { w += x; x = 0; }
    if (y < 0) { h += y; y = 0; }
  
    if ((x + w) > _width)  w = _width  - x;
    if ((y + h) > _height) h = _height - y;
  
    if ((w < 1) || (h < 1)) return;
  
    spi_begin();
  
    setWindow(x, y, x + w - 1, y + h - 1);
  
    uint32_t n = (uint32_t)w * (uint32_t)h;
  
  #ifdef RPI_WRITE_STROBE
    tft_Write_16(color);
    while(n--) {WR_L; WR_H;}
  #else
    #ifdef ESP32_PARALLEL
      if (color>>8 == (uint8_t)color)
      {
        tft_Write_8(color);
        n--; WR_L; WR_H;
        while (n) {WR_L; WR_H; n--; WR_L; WR_H;}
      }
      else
      {
        while (n--) {tft_Write_16(color);}
      }
    #else
      writeBlock(color, n);
    #endif
  #endif
  
    spi_end();
  }
  #endif
  
  /***************************************************************************************
  ** Function name:           color565
  ** Description:             convert three 8 bit RGB levels to a 16 bit colour value
  ***************************************************************************************/
  uint16_t TFT_eSPI::color565(uint8_t r, uint8_t g, uint8_t b)
  {
    return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
  }
  
  
  /***************************************************************************************
  ** Function name:           color16to8
  ** Description:             convert 16 bit colour to an 8 bit 332 RGB colour value
  ***************************************************************************************/
  uint8_t TFT_eSPI::color16to8(uint16_t c)
  {
    return ((c & 0xE000)>>8) | ((c & 0x0700)>>6) | ((c & 0x0018)>>3);
  }
  
  
  /***************************************************************************************
  ** Function name:           color8to16
  ** Description:             convert 8 bit colour to a 16 bit 565 colour value
  ***************************************************************************************/
  uint16_t TFT_eSPI::color8to16(uint8_t color)
  {
    uint8_t  blue[] = {0, 11, 21, 31}; // blue 2 to 5 bit colour lookup table
    uint16_t color16 = 0;
  
    //        =====Green=====     ===============Red==============
    color16  = (color & 0x1C)<<6 | (color & 0xC0)<<5 | (color & 0xE0)<<8;
    //        =====Green=====    =======Blue======
    color16 |= (color & 0x1C)<<3 | blue[color & 0x03];
  
    return color16;
  }
  
  
  /***************************************************************************************
  ** Function name:           invertDisplay
  ** Description:             invert the display colours i = 1 invert, i = 0 normal
  ***************************************************************************************/
  void TFT_eSPI::invertDisplay(boolean i)
  {
    spi_begin();
    // Send the command twice as otherwise it does not always work!
    writecommand(i ? TFT_INVON : TFT_INVOFF);
    writecommand(i ? TFT_INVON : TFT_INVOFF);
    spi_end();
  }
  
  
  /**************************************************************************
  ** Function name:           setAttribute
  ** Description:             Sets a control parameter of an attribute
  **************************************************************************/
  void TFT_eSPI::setAttribute(uint8_t attr_id, uint8_t param) {
      switch (attr_id) {
              break;
          case 1:
              _cp437 = param;
              break;
          case 2:
              _utf8  = param;
              decoderState = 0;
              break;
          //case 3: // TBD future feature control
          //    _tbd = param;
          //    break;
      }
  }
  
  
  /**************************************************************************
  ** Function name:           getAttribute
  ** Description:             Get value of an attribute (control parameter)
  **************************************************************************/
  uint8_t TFT_eSPI::getAttribute(uint8_t attr_id) {
      switch (attr_id) {
          case 1: // ON/OFF control of full CP437 character set
              return _cp437;
              break;
          case 2: // ON/OFF control of UTF-8 decoding
              return _utf8;
              break;
          //case 3: // TBD future feature control
          //    return _tbd;
          //    break;
      }
  
      return false;
  }
  
  /***************************************************************************************
  ** Function name:           decodeUTF8
  ** Description:             *************************************************************************************x*/
  #define DECODE_UTF8 // Test only, comment out to stop decoding
  uint16_t TFT_eSPI::decodeUTF8(uint8_t c)
  {
  #ifdef DECODE_UTF8
    // 7 bit Unicode Code Point
    if ((c & 0x80) == 0x00) {
      decoderState = 0;
      return (uint16_t)c;
    }
  
    if (decoderState == 0)
    {
      // 11 bit Unicode Code Point
      if ((c & 0xE0) == 0xC0)
      {
        decoderBuffer = ((c & 0x1F)<<6);
        decoderState = 1;
        return 0;
      }
  
      // 16 bit Unicode Code Point
      if ((c & 0xF0) == 0xE0)
      {
        decoderBuffer = ((c & 0x0F)<<12);
        decoderState = 2;
        return 0;
      }
      // 21 bit Unicode  Code Point not supported so fall-back to extended ASCII
      // if ((c & 0xF8) == 0xF0) return (uint16_t)c;
    }
    else
    {
      if (decoderState == 2)
      {
        decoderBuffer |= ((c & 0x3F)<<6);
        decoderState--;
        return 0;
      }
      else
      {
        decoderBuffer |= (c & 0x3F);
        decoderState = 0;
        return decoderBuffer;
      }
    }
  
    decoderState = 0;
  #endif
  
    return (uint16_t)c; // fall-back to extended ASCII
  }
  
  
  /***************************************************************************************
  ** Function name:           decodeUTF8
  ** Description:             Line buffer UTF-8 decoder with fall-back to extended ASCII
  *************************************************************************************x*/
  uint16_t TFT_eSPI::decodeUTF8(uint8_t *buf, uint16_t *index, uint16_t remaining)
  {
    uint16_t c = buf[(*index)++];
    //
  #ifdef DECODE_UTF8
    // 7 bit Unicode
    if ((c & 0x80) == 0x00) return c;
  
    // 11 bit Unicode
    if (((c & 0xE0) == 0xC0) && (remaining > 1))
      return ((c & 0x1F)<<6) | (buf[(*index)++]&0x3F);
  
    // 16 bit Unicode
    if (((c & 0xF0) == 0xE0) && (remaining > 2))
    {
      c = ((c & 0x0F)<<12) | ((buf[(*index)++]&0x3F)<<6);
      return  c | ((buf[(*index)++]&0x3F));
    }
  
    // 21 bit Unicode not supported so fall-back to extended ASCII
    // if ((c & 0xF8) == 0xF0) return c;
  #endif
  
    return c; // fall-back to extended ASCII
  }
  
  
  /***************************************************************************************
  ** Function name:           write
  ** Description:             draw characters piped through serial stream
  ***************************************************************************************/
  size_t TFT_eSPI::write(uint8_t utf8)
  {
    if (utf8 == '\r') return 1;
  
    uint16_t uniCode = utf8;
  
    if (_utf8) uniCode = decodeUTF8(utf8);
  
    if (uniCode == 0) return 1;
  
  #ifdef SMOOTH_FONT
    if(fontLoaded)
    {
      //    //
      //fontFile = SPIFFS.open( _gFontFilename, "r" );
  
      //if(!fontFile)
      //{
      //  fontLoaded = false;
      //  return 1;
      //}
  
      drawGlyph(uniCode);
  
      //fontFile.close();
      return 1;
    }
  #endif
  
    if (uniCode == '\n') uniCode+=22; // Make it a valid space character to stop errors
    else if (uniCode < 32) return 1;
  
    uint16_t width = 0;
    uint16_t height = 0;
  
  //vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv DEBUG vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
    //  //  //delay(5);                     // Debug optional wait for serial port to flush through
  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DEBUG ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  
  //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  #ifdef LOAD_GFXFF
    if(!gfxFont) {
  #endif
  //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  
  #ifdef LOAD_FONT2
    if (textfont == 2)
    {
      if (uniCode > 127) return 1;
  
      width = pgm_read_byte(widtbl_f16 + uniCode-32);
      height = chr_hgt_f16;
      // Font 2 is rendered in whole byte widths so we must allow for this
      width = (width + 6) / 8;  // Width in whole bytes for font 2, should be + 7 but must allow for font width change
      width = width * 8;        // Width converted back to pixels
    }
    #ifdef LOAD_RLE
    else
    #endif
  #endif
  
  #ifdef LOAD_RLE
    {
      if ((textfont>2) && (textfont<9))
      {
        if (uniCode > 127) return 1;
        // Uses the fontinfo struct array to avoid lots of 'if' or 'switch' statements
        width = pgm_read_byte( (uint8_t *)pgm_read_dword( &(fontdata[textfont].widthtbl ) ) + uniCode-32 );
        height= pgm_read_byte( &fontdata[textfont].height );
      }
    }
  #endif
  
  #ifdef LOAD_GLCD
    if (textfont==1)
    {
        width =  6;
        height = 8;
    }
  #else
    if (textfont==1) return 1;
  #endif
  
    height = height * textsize;
  
    if (utf8 == '\n') {
      cursor_y += height;
      cursor_x  = 0;
    }
    else
    {
      if (textwrapX && (cursor_x + width * textsize > _width))
      {
        cursor_y += height;
        cursor_x = 0;
      }
      if (textwrapY && (cursor_y >= (int32_t)_height)) cursor_y = 0;
      cursor_x += drawChar(uniCode, cursor_x, cursor_y, textfont);
    }
  
  //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  #ifdef LOAD_GFXFF
    } // Custom GFX font
    else
    {
      if(utf8 == '\n') {
        cursor_x  = 0;
        cursor_y += (int16_t)textsize *
                    (uint8_t)pgm_read_byte(&gfxFont->yAdvance);
      } else {
  
        bool is_in_block_flag = false;
        uint16_t c2 = uniCode;
        #ifdef USE_M5_FONT_CREATOR
        int32_t index = -1;
        index = getUnicodeFontIndex(c2);
        if (index != -1) {
          c2 = index;
          is_in_block_flag = true;
        }
        #else
        if ((c2 >= pgm_read_word(&gfxFont->first)) && (c2 <= pgm_read_word(&gfxFont->last )))
        {
          is_in_block_flag = true;
          c2 -= pgm_read_word(&gfxFont->first);
        }
        #endif
  
        if (is_in_block_flag == false) return 1;
        GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c2]);
        uint8_t   w     = pgm_read_byte(&glyph->width),
                  h     = pgm_read_byte(&glyph->height);
        if((w > 0) && (h > 0)) { // Is there an associated bitmap?
          int16_t xo = (int8_t)pgm_read_byte(&glyph->xOffset);
          if(textwrapX && ((cursor_x + textsize * (xo + w)) > _width)) {
            // Drawing character would go off right edge; wrap to new line
            cursor_x  = 0;
            cursor_y += (int16_t)textsize *
                        (uint8_t)pgm_read_byte(&gfxFont->yAdvance);
          }
          if (textwrapY && (cursor_y >= (int32_t)_height)) cursor_y = 0;
          drawChar(cursor_x, cursor_y, uniCode, textcolor, textbgcolor, textsize);
        }
        cursor_x += pgm_read_byte(&glyph->xAdvance) * (int16_t)textsize;
      }
    }
  #endif // LOAD_GFXFF
  //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  
    return 1;
  }
  
  
  /***************************************************************************************
  ** Function name:           drawChar
  ** Description:             draw a Unicode glyph onto the screen
  ***************************************************************************************/
    // Any UTF-8 decoding must be done before calling drawChar()
  int16_t TFT_eSPI::drawChar(uint16_t uniCode, int32_t x, int32_t y)
  {
    return drawChar(uniCode, x, y, textfont);
  }
  
    // Any UTF-8 decoding must be done before calling drawChar()
  int16_t TFT_eSPI::drawChar(uint16_t uniCode, int32_t x, int32_t y, uint8_t font)
  {
    if (!uniCode) return 0;
  
    if (font==1)
    {
  #ifdef LOAD_GLCD
    #ifndef LOAD_GFXFF
      drawChar(x, y, uniCode, textcolor, textbgcolor, textsize);
      return 6 * textsize;
    #endif
  #else
    #ifndef LOAD_GFXFF
      return 0;
    #endif
  #endif
  
  #ifdef LOAD_GFXFF
      drawChar(x, y, uniCode, textcolor, textbgcolor, textsize);
      if(!gfxFont) { // 'Classic' built-in font
      #ifdef LOAD_GLCD
        return 6 * textsize;
      #else
        return 0;
      #endif
      }
      else
      {
        
        bool is_in_block_flag = false;
        uint16_t c2 = uniCode;
        #ifdef USE_M5_FONT_CREATOR
        int32_t index = -1;
        index = getUnicodeFontIndex(c2);
        if (index != -1) {
          c2 = index;
          is_in_block_flag = true;
        }
        #else
        if ((c2 >= pgm_read_word(&gfxFont->first)) && (c2 <= pgm_read_word(&gfxFont->last )))
        {
          is_in_block_flag = true;
          c2 -= pgm_read_word(&gfxFont->first);
        }
        #endif
        if (is_in_block_flag == true)
        //if((uniCode >= pgm_read_word(&gfxFont->first)) && (uniCode <= pgm_read_word(&gfxFont->last) ))
        {
          //uint16_t   c2    = uniCode - pgm_read_word(&gfxFont->first);
          GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c2]);
          return pgm_read_byte(&glyph->xAdvance) * textsize;
        }
        else
        {
          return 0;
        }
      }
  #endif
    }
  
    if ((font>1) && (font<9) && ((uniCode < 32) || (uniCode > 127))) return 0;
  
    int32_t width  = 0;
    int32_t height = 0;
    uint32_t flash_address = 0;
    uniCode -= 32;
  
  #ifdef LOAD_FONT2
    if (font == 2)
    {
      flash_address = pgm_read_dword(&chrtbl_f16[uniCode]);
      width = pgm_read_byte(widtbl_f16 + uniCode);
      height = chr_hgt_f16;
    }
    #ifdef LOAD_RLE
    else
    #endif
  #endif
  
  #ifdef LOAD_RLE
    {
      if ((font>2) && (font<9))
      {
        flash_address = pgm_read_dword( (const void*)(pgm_read_dword( &(fontdata[font].chartbl ) ) + uniCode*sizeof(void *)) );
        width = pgm_read_byte( (uint8_t *)pgm_read_dword( &(fontdata[font].widthtbl ) ) + uniCode );
        height= pgm_read_byte( &fontdata[font].height );
      }
    }
  #endif
  
    int32_t w = width;
    int32_t pX      = 0;
    int32_t pY      = y;
    uint8_t line = 0;
  
  #ifdef LOAD_FONT2 // chop out code if we do not need it
    if (font == 2) {
      w = w + 6; // Should be + 7 but we need to compensate for width increment
      w = w / 8;
      if (x + width * textsize >= (int16_t)_width) return width * textsize ;
  
      if (textcolor == textbgcolor || textsize != 1) {
        //spi_begin();          // Sprite class can use this function, avoiding spi_begin()
        inTransaction = true;
  
        for (int32_t i = 0; i < height; i++)
        {
          if (textcolor != textbgcolor) fillRect(x, pY, width * textsize, textsize, textbgcolor);
  
          for (int32_t k = 0; k < w; k++)
          {
            line = pgm_read_byte((uint8_t *)flash_address + w * i + k);
            if (line) {
              if (textsize == 1) {
                pX = x + k * 8;
                if (line & 0x80) drawPixel(pX, pY, textcolor);
                if (line & 0x40) drawPixel(pX + 1, pY, textcolor);
                if (line & 0x20) drawPixel(pX + 2, pY, textcolor);
                if (line & 0x10) drawPixel(pX + 3, pY, textcolor);
                if (line & 0x08) drawPixel(pX + 4, pY, textcolor);
                if (line & 0x04) drawPixel(pX + 5, pY, textcolor);
                if (line & 0x02) drawPixel(pX + 6, pY, textcolor);
                if (line & 0x01) drawPixel(pX + 7, pY, textcolor);
              }
              else {
                pX = x + k * 8 * textsize;
                if (line & 0x80) fillRect(pX, pY, textsize, textsize, textcolor);
                if (line & 0x40) fillRect(pX + textsize, pY, textsize, textsize, textcolor);
                if (line & 0x20) fillRect(pX + 2 * textsize, pY, textsize, textsize, textcolor);
                if (line & 0x10) fillRect(pX + 3 * textsize, pY, textsize, textsize, textcolor);
                if (line & 0x08) fillRect(pX + 4 * textsize, pY, textsize, textsize, textcolor);
                if (line & 0x04) fillRect(pX + 5 * textsize, pY, textsize, textsize, textcolor);
                if (line & 0x02) fillRect(pX + 6 * textsize, pY, textsize, textsize, textcolor);
                if (line & 0x01) fillRect(pX + 7 * textsize, pY, textsize, textsize, textcolor);
              }
            }
          }
          pY += textsize;
        }
  
        inTransaction = false;
        spi_end();
      }
      else
        // Faster drawing of characters and background using block write
      {
        spi_begin();
  
        setWindow(x, y, x + width - 1, y + height - 1);
  
        uint8_t mask;
        for (int32_t i = 0; i < height; i++)
        {
          pX = width;
          for (int32_t k = 0; k < w; k++)
          {
            line = pgm_read_byte((uint8_t *) (flash_address + w * i + k) );
            mask = 0x80;
            while (mask && pX) {
              if (line & mask) {tft_Write_16(textcolor);}
              else {tft_Write_16(textbgcolor);}
              pX--;
              mask = mask >> 1;
            }
          }
          if (pX) {tft_Write_16(textbgcolor);}
        }
  
        spi_end();
      }
    }
  
    #ifdef LOAD_RLE
    else
    #endif
  #endif  //FONT2
  
  #ifdef LOAD_RLE  //674 bytes of code
    // Font is not 2 and hence is RLE encoded
    {
      spi_begin();
      inTransaction = true;
  
      w *= height; // Now w is total number of pixels in the character
      if ((textsize != 1) || (textcolor == textbgcolor)) {
        if (textcolor != textbgcolor) fillRect(x, pY, width * textsize, textsize * height, textbgcolor);
        int32_t px = 0, py = pY; // To hold character block start and end column and row values
        int32_t pc = 0; // Pixel count
        uint8_t np = textsize * textsize; // Number of pixels in a drawn pixel
  
        uint8_t tnp = 0; // Temporary copy of np for while loop
        uint8_t ts = textsize - 1; // Temporary copy of textsize
        // 16 bit pixel count so maximum font size is equivalent to 180x180 pixels in area
        // w is total number of pixels to plot to fill character block
        while (pc < w)
        {
          line = pgm_read_byte((uint8_t *)flash_address);
          flash_address++;
          if (line & 0x80) {
            line &= 0x7F;
            line++;
            if (ts) {
              px = x + textsize * (pc % width); // Keep these px and py calculations outside the loop as they are slow
              py = y + textsize * (pc / width);
            }
            else {
              px = x + pc % width; // Keep these px and py calculations outside the loop as they are slow
              py = y + pc / width;
            }
            while (line--) { // In this case the while(line--) is faster
              pc++; // This is faster than putting pc+=line before while()?
              setWindow(px, py, px + ts, py + ts);
  
              if (ts) {
                tnp = np;
                while (tnp--) {tft_Write_16(textcolor);}
              }
              else {tft_Write_16(textcolor);}
              px += textsize;
  
              if (px >= (x + width * textsize))
              {
                px = x;
                py += textsize;
              }
            }
          }
          else {
            line++;
            pc += line;
          }
        }
      }
      else // Text colour != background && textsize = 1
           // so use faster drawing of characters and background using block write
      {
        setWindow(x, y, x + width - 1, y + height - 1);
  
  #ifdef RPI_WRITE_STROBE
        uint8_t textcolorBin[] = { (uint8_t) (textcolor >> 8), (uint8_t) textcolor };
        uint8_t textbgcolorBin[] = { (uint8_t) (textbgcolor >> 8), (uint8_t) textbgcolor };
  #endif
  
        // Maximum font size is equivalent to 180x180 pixels in area
        while (w > 0)
        {
          line = pgm_read_byte((uint8_t *)flash_address++); // 8 bytes smaller when incrementing here
          if (line & 0x80) {
            line &= 0x7F;
            line++; w -= line;
  #ifdef RPI_WRITE_STROBE
            spi.writePattern(&textcolorBin[0], 2, 1); line--;
            while(line--) {WR_L; WR_H;}
  #else
            #ifdef ESP32_PARALLEL
              while (line--) {tft_Write_16(textcolor);}
            #else
              writeBlock(textcolor,line);
            #endif
  #endif
          }
          else {
            line++; w -= line;
  #ifdef RPI_WRITE_STROBE
            spi.writePattern(&textbgcolorBin[0], 2, 1); line--;
            while(line--) {WR_L; WR_H;}
  #else
            #ifdef ESP32_PARALLEL
              while (line--) {tft_Write_16(textbgcolor);}
            #else
              writeBlock(textbgcolor,line);
            #endif
  #endif
          }
        }
      }
      inTransaction = false;
      spi_end();
    }
    // End of RLE font rendering
  #endif
    return width * textsize;    // x +
  }
  
  
  /***************************************************************************************
  ** Function name:           drawString (with or without user defined font)
  ** Description :            draw string with padding if it is defined
  ***************************************************************************************/
  // Without font number, uses font set by setTextFont()
  int16_t TFT_eSPI::drawString(const String& string, int32_t poX, int32_t poY)
  {
    int16_t len = string.length() + 2;
    char buffer[len];
    string.toCharArray(buffer, len);
    return drawString(buffer, poX, poY, textfont);
  }
  // With font number
  int16_t TFT_eSPI::drawString(const String& string, int32_t poX, int32_t poY, uint8_t font)
  {
    int16_t len = string.length() + 2;
    char buffer[len];
    string.toCharArray(buffer, len);
    return drawString(buffer, poX, poY, font);
  }
  
  // Without font number, uses font set by setTextFont()
  int16_t TFT_eSPI::drawString(const char *string, int32_t poX, int32_t poY)
  {
    return drawString(string, poX, poY, textfont);
  }
  
  // With font number. Note: font number is over-ridden if a smooth font is loaded
  int16_t TFT_eSPI::drawString(const char *string, int32_t poX, int32_t poY, uint8_t font)
  {
    int16_t sumX = 0;
    uint8_t padding = 1, baseline = 0;
    uint16_t cwidth = textWidth(string, font); // Find the pixel width of the string in the font
    uint16_t cheight = 8 * textsize;
  
  #ifdef LOAD_GFXFF
    #ifdef SMOOTH_FONT
      bool freeFont = (font == 1 && gfxFont && !fontLoaded);
    #else
      bool freeFont = (font == 1 && gfxFont);
    #endif
  
    if (freeFont) {
      cheight = glyph_ab * textsize;
      poY += cheight; // Adjust for baseline datum of free fonts
      baseline = cheight;
      padding =101; // Different padding method used for Free Fonts
  
      // We need to make an adjustment for the bottom of the string (eg 'y' character)
      if ((textdatum == BL_DATUM) || (textdatum == BC_DATUM) || (textdatum == BR_DATUM)) {
        cheight += glyph_bb * textsize;
      }
    }
  #endif
  
  
    // If it is not font 1 (GLCD or free font) get the baseline and pixel height of the font
  #ifdef SMOOTH_FONT
    if(fontLoaded) {
      baseline = gFont.maxAscent;
      cheight  = fontHeight();
    }
    else
  #endif
    if (font!=1) {
      baseline = pgm_read_byte( &fontdata[font].baseline ) * textsize;
      cheight = fontHeight(font);
    }
  
    if (textdatum || padX)
    {
  
      switch(textdatum) {
        case TC_DATUM:
          poX -= cwidth/2;
          padding += 1;
          break;
        case TR_DATUM:
          poX -= cwidth;
          padding += 2;
          break;
        case ML_DATUM:
          poY -= cheight/2;
          //padding += 0;
          break;
        case MC_DATUM:
          poX -= cwidth/2;
          poY -= cheight/2;
          padding += 1;
          break;
        case MR_DATUM:
          poX -= cwidth;
          poY -= cheight/2;
          padding += 2;
          break;
        case BL_DATUM:
          poY -= cheight;
          //padding += 0;
          break;
        case BC_DATUM:
          poX -= cwidth/2;
          poY -= cheight;
          padding += 1;
          break;
        case BR_DATUM:
          poX -= cwidth;
          poY -= cheight;
          padding += 2;
          break;
        case L_BASELINE:
          poY -= baseline;
          //padding += 0;
          break;
        case C_BASELINE:
          poX -= cwidth/2;
          poY -= baseline;
          padding += 1;
          break;
        case R_BASELINE:
          poX -= cwidth;
          poY -= baseline;
          padding += 2;
          break;
      }
      // Check coordinates are OK, adjust if not
      if (poX < 0) poX = 0;
      if (poX+cwidth > width())   poX = width() - cwidth;
      if (poY < 0) poY = 0;
      if (poY+cheight-baseline> height()) poY = height() - cheight;
    }
  
  
    int8_t xo = 0;
  #ifdef LOAD_GFXFF
    if (freeFont && (textcolor!=textbgcolor))
      {
        cheight = (glyph_ab + glyph_bb) * textsize;
        // Get the offset for the first character only to allow for negative offsets
        uint16_t c2 = 0;
        uint16_t len = strlen(string);
        uint16_t n = 0;
  
        while (n < len && c2 == 0) c2 = decodeUTF8((uint8_t*)string, &n, len - n);
  
        bool is_in_block_flag = false;
        #ifdef USE_M5_FONT_CREATOR
        int32_t index = -1;
        index = getUnicodeFontIndex(c2);
        if (index != -1) {
          c2 = index;
          is_in_block_flag = true;
        }
        #else
        if ((c2 >= pgm_read_word(&gfxFont->first)) && (c2 <= pgm_read_word(&gfxFont->last )))
        {
          is_in_block_flag = true;
          c2 -= pgm_read_word(&gfxFont->first);
        }
        #endif
  
        if (is_in_block_flag == true)
        //if((c2 >= pgm_read_word(&gfxFont->first)) && (c2 <= pgm_read_word(&gfxFont->last) ))
        {
          //c2 -= pgm_read_word(&gfxFont->first);
          GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c2]);
          xo = pgm_read_byte(&glyph->xOffset) * textsize;
          // Adjust for negative xOffset
          if (xo > 0) xo = 0;
          else cwidth -= xo;
          // Add 1 pixel of padding all round
          //cheight +=2;
          //fillRect(poX+xo-1, poY - 1 - glyph_ab * textsize, cwidth+2, cheight, textbgcolor);
          fillRect(poX+xo, poY - glyph_ab * textsize, cwidth, cheight, textbgcolor);
        }
        padding -=100;
      }
  #endif
    uint16_t len = strlen(string);
    uint16_t n = 0;
  
  #ifdef SMOOTH_FONT
    if(fontLoaded)
    {
      if (textcolor!=textbgcolor) fillRect(poX, poY, cwidth, cheight, textbgcolor);
      //drawLine(poX - 5, poY, poX + 5, poY, TFT_GREEN);
      //drawLine(poX, poY - 5, poX, poY + 5, TFT_GREEN);
      //fontFile = SPIFFS.open( _gFontFilename, "r");
      if(!fontFile) return 0;
  
      setCursor(poX, poY);
  
      while (n < len)
      {
        uint16_t uniCode = decodeUTF8((uint8_t*)string, &n, len - n);
        drawGlyph(uniCode);
      }
      sumX += cwidth;
      //fontFile.close();
    }
    else
  #endif
    {
      while (n < len)
      {
        uint16_t uniCode = decodeUTF8((uint8_t*)string, &n, len - n);
        sumX += drawChar(uniCode, poX+sumX, poY, font);
      }
    }
  
  //vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv DEBUG vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
  // Switch on debugging for the padding areas
  //#define PADDING_DEBUG
  
  #ifndef PADDING_DEBUG
  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DEBUG ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  
    if((padX>cwidth) && (textcolor!=textbgcolor))
    {
      int16_t padXc = poX+cwidth+xo;
  #ifdef LOAD_GFXFF
      if (freeFont)
      {
        poX +=xo; // Adjust for negative offset start character
        poY -= glyph_ab * textsize;
        sumX += poX;
      }
  #endif
      switch(padding) {
        case 1:
          fillRect(padXc,poY,padX-cwidth,cheight, textbgcolor);
          break;
        case 2:
          fillRect(padXc,poY,(padX-cwidth)>>1,cheight, textbgcolor);
          padXc = (padX-cwidth)>>1;
          if (padXc>poX) padXc = poX;
          fillRect(poX - padXc,poY,(padX-cwidth)>>1,cheight, textbgcolor);
          break;
        case 3:
          if (padXc>padX) padXc = padX;
          fillRect(poX + cwidth - padXc,poY,padXc-cwidth,cheight, textbgcolor);
          break;
      }
    }
  
  
  #else
  
  //vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv DEBUG vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
  // This is debug code to show text (green box) and blanked (white box) areas
  // It shows that the padding areas are being correctly sized and positioned
  
    if((padX>sumX) && (textcolor!=textbgcolor))
    {
      int16_t padXc = poX+sumX; // Maximum left side padding
  #ifdef LOAD_GFXFF
      if ((font == 1) && (gfxFont)) poY -= glyph_ab;
  #endif
      drawRect(poX,poY,sumX,cheight, TFT_GREEN);
      switch(padding) {
        case 1:
          drawRect(padXc,poY,padX-sumX,cheight, TFT_WHITE);
          break;
        case 2:
          drawRect(padXc,poY,(padX-sumX)>>1, cheight, TFT_WHITE);
          padXc = (padX-sumX)>>1;
          if (padXc>poX) padXc = poX;
          drawRect(poX - padXc,poY,(padX-sumX)>>1,cheight, TFT_WHITE);
          break;
        case 3:
          if (padXc>padX) padXc = padX;
          drawRect(poX + sumX - padXc,poY,padXc-sumX,cheight, TFT_WHITE);
          break;
      }
    }
  #endif
  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DEBUG ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  
  return sumX;
  }
  
  
  /***************************************************************************************
  ** Function name:           drawCentreString (deprecated, use setTextDatum())
  ** Descriptions:            draw string centred on dX
  ***************************************************************************************/
  int16_t TFT_eSPI::drawCentreString(const String& string, int32_t dX, int32_t poY, uint8_t font)
  {
    int16_t len = string.length() + 2;
    char buffer[len];
    string.toCharArray(buffer, len);
    return drawCentreString(buffer, dX, poY, font);
  }
  
  int16_t TFT_eSPI::drawCentreString(const char *string, int32_t dX, int32_t poY, uint8_t font)
  {
    uint8_t tempdatum = textdatum;
    int32_t sumX = 0;
    textdatum = TC_DATUM;
    sumX = drawString(string, dX, poY, font);
    textdatum = tempdatum;
    return sumX;
  }
  
  
  /***************************************************************************************
  ** Function name:           drawRightString (deprecated, use setTextDatum())
  ** Descriptions:            draw string right justified to dX
  ***************************************************************************************/
  int16_t TFT_eSPI::drawRightString(const String& string, int32_t dX, int32_t poY, uint8_t font)
  {
    int16_t len = string.length() + 2;
    char buffer[len];
    string.toCharArray(buffer, len);
    return drawRightString(buffer, dX, poY, font);
  }
  
  int16_t TFT_eSPI::drawRightString(const char *string, int32_t dX, int32_t poY, uint8_t font)
  {
    uint8_t tempdatum = textdatum;
    int16_t sumX = 0;
    textdatum = TR_DATUM;
    sumX = drawString(string, dX, poY, font);
    textdatum = tempdatum;
    return sumX;
  }
  
  
  /***************************************************************************************
  ** Function name:           drawNumber
  ** Description:             draw a long integer
  ***************************************************************************************/
  int16_t TFT_eSPI::drawNumber(long long_num, int32_t poX, int32_t poY)
  {
    isDigits = true; // Eliminate jiggle in monospaced fonts
    char str[12];
    ltoa(long_num, str, 10);
    return drawString(str, poX, poY, textfont);
  }
  
  int16_t TFT_eSPI::drawNumber(long long_num, int32_t poX, int32_t poY, uint8_t font)
  {
    isDigits = true; // Eliminate jiggle in monospaced fonts
    char str[12];
    ltoa(long_num, str, 10);
    return drawString(str, poX, poY, font);
  }
  
  
  /***************************************************************************************
  ** Function name:           drawFloat
  ** Descriptions:            drawFloat, prints 7 non zero digits maximum
  ***************************************************************************************/
  // Assemble and print a string, this permits alignment relative to a datum
  // looks complicated but much more compact and actually faster than using print class
  int16_t TFT_eSPI::drawFloat(float floatNumber, uint8_t dp, int32_t poX, int32_t poY)
  {
    return drawFloat(floatNumber, dp, poX, poY, textfont);
  }
  
  int16_t TFT_eSPI::drawFloat(float floatNumber, uint8_t dp, int32_t poX, int32_t poY, uint8_t font)
  {
    isDigits = true;
    char str[14];               // Array to contain decimal string
    uint8_t ptr = 0;            // Initialise pointer for array
    int8_t  digits = 1;         // Count the digits to avoid array overflow
    float rounding = 0.5;       // Round up down delta
  
    if (dp > 7) dp = 7; // Limit the size of decimal portion
  
    // Adjust the rounding value
    for (uint8_t i = 0; i < dp; ++i) rounding /= 10.0;
  
    if (floatNumber < -rounding)    // add sign, avoid adding - sign to 0.0!
    {
      str[ptr++] = '-'; // Negative number
      str[ptr] = 0; // Put a null in the array as a precaution
      digits = 0;   // Set digits to 0 to compensate so pointer value can be used later
      floatNumber = -floatNumber; // Make positive
    }
  
    floatNumber += rounding; // Round up or down
  
    // For error put ... in string and return (all TFT_eSPI library fonts contain . character)
    if (floatNumber >= 2147483647) {
      strcpy(str, "...");
      return drawString(str, poX, poY, font);
    }
    // No chance of overflow from here on
  
    // Get integer part
    uint32_t temp = (uint32_t)floatNumber;
  
    // Put integer part into array
    ltoa(temp, str + ptr, 10);
  
    // Find out where the null is to get the digit count loaded
    while ((uint8_t)str[ptr] != 0) ptr++; // Move the pointer along
    digits += ptr;                  // Count the digits
  
    str[ptr++] = '.'; // Add decimal point
    str[ptr] = '0';   // Add a dummy zero
    str[ptr + 1] = 0; // Add a null but don't increment pointer so it can be overwritten
  
    // Get the decimal portion
    floatNumber = floatNumber - temp;
  
    // Get decimal digits one by one and put in array
    // Limit digit count so we don't get a false sense of resolution
    uint8_t i = 0;
    while ((i < dp) && (digits < 9)) // while (i < dp) for no limit but array size must be increased
    {
      i++;
      floatNumber *= 10;       // for the next decimal
      temp = floatNumber;      // get the decimal
      ltoa(temp, str + ptr, 10);
      ptr++; digits++;         // Increment pointer and digits count
      floatNumber -= temp;     // Remove that digit
    }
  
    // Finally we can plot the string and return pixel length
    return drawString(str, poX, poY, font);
  }
  
  
  /***************************************************************************************
  ** Function name:           setFreeFont
  ** Descriptions:            Sets the GFX free font to use
  ***************************************************************************************/
  
  #ifdef LOAD_GFXFF
  
  void TFT_eSPI::setFreeFont(const GFXfont *f)
  {
    if (f == nullptr) // Fix issue #400 (ESP32 crash)
    {
      setTextFont(1); // Use GLCD font
      return;
    }
  
    textfont = 1;
    gfxFont = (GFXfont *)f;
  
    glyph_ab = 0;
    glyph_bb = 0;
  
    uint16_t numChars = 0;
    #ifdef USE_M5_FONT_CREATOR
    if(pgm_read_word(&gfxFont->range_num) != 0)
    {
      EncodeRange *range_pst = (EncodeRange *)pgm_read_dword(&gfxFont->range);
      _smooth_bpp = gfxFont->smooth_bpp;
      for(uint16_t i = 0; i < pgm_read_word(&gfxFont->range_num); i++)
      {
        numChars += pgm_read_word(&range_pst[i].end) - pgm_read_word(&range_pst[i].start);
      }
    }
    else
    {
      numChars = pgm_read_word(&gfxFont->last) - pgm_read_word(&gfxFont->first);
    }
    #else
    numChars = pgm_read_word(&gfxFont->last) - pgm_read_word(&gfxFont->first);
    #endif
    
    
    // Find the biggest above and below baseline offsets
    for (uint16_t c = 0; c < numChars; c++)
    {
      GFXglyph *glyph1  = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c]);
      int8_t ab = -pgm_read_byte(&glyph1->yOffset);
      if (ab > glyph_ab) glyph_ab = ab;
      int8_t bb = pgm_read_byte(&glyph1->height) - ab;
      if (bb > glyph_bb) glyph_bb = bb;
    }
  }
  
  
  /***************************************************************************************
  ** Function name:           setTextFont
  ** Description:             Set the font for the print stream
  ***************************************************************************************/
  void TFT_eSPI::setTextFont(uint8_t f)
  {
    textfont = (f > 0) ? f : 1; // Don't allow font 0
    gfxFont = NULL;
  }
  
  #else
  
      
  /***************************************************************************************
  ** Function name:           setFreeFont
  ** Descriptions:            Sets the GFX free font to use
  ***************************************************************************************/
  
  // Alternative to setTextFont() so we don't need two different named functions
  void TFT_eSPI::setFreeFont(uint8_t font)
  {
    setTextFont(font);
  }
  
  
  /***************************************************************************************
  ** Function name:           setTextFont
  ** Description:             Set the font for the print stream
  ***************************************************************************************/
  void TFT_eSPI::setTextFont(uint8_t f)
  {
    textfont = (f > 0) ? f : 1; // Don't allow font 0
  }
  
  #endif
  
  
  /***************************************************************************************
  ** Function name:           writeBlock
  ** Description:             Write a block of pixels of the same colour
  ***************************************************************************************/
  //Clear screen test 76.8ms theoretical. 81.5ms TFT_eSPI, 967ms Adafruit_ILI9341
  //Performance 26.15Mbps@26.66MHz, 39.04Mbps@40MHz, 75.4Mbps@80MHz SPI clock
  //Efficiency:
  //       TFT_eSPI       98.06%              97.59%          94.24%
  //       Adafruit_GFX   19.62%              14.31%           7.94%
  //
  #if defined (ESP8266) && !defined (ILI9488_DRIVER)
  void writeBlock(uint16_t color, uint32_t repeat)
  {
    uint16_t color16 = (color >> 8) | (color << 8);
    uint32_t color32 = color16 | color16 << 16;
  
    SPI1W0 = color32;
    SPI1W1 = color32;
    SPI1W2 = color32;
    SPI1W3 = color32;
    if (repeat > 8)
    {
      SPI1W4 = color32;
      SPI1W5 = color32;
      SPI1W6 = color32;
      SPI1W7 = color32;
    }
    if (repeat > 16)
    {
      SPI1W8 = color32;
      SPI1W9 = color32;
      SPI1W10 = color32;
      SPI1W11 = color32;
    }
    if (repeat > 24)
    {
      SPI1W12 = color32;
      SPI1W13 = color32;
      SPI1W14 = color32;
      SPI1W15 = color32;
    }
    if (repeat > 31)
    {
      SPI1U1 = (511 << SPILMOSI);
      while(repeat>31)
      {
  #if defined SPI_FREQUENCY && (SPI_FREQUENCY == 80000000)
        if(SPI1CMD & SPIBUSY) // added to sync with flag change
  #endif
        while(SPI1CMD & SPIBUSY) {}
        SPI1CMD |= SPIBUSY;
        repeat -= 32;
      }
      while(SPI1CMD & SPIBUSY) {}
    }
  
    if (repeat)
    {
      repeat = (repeat << 4) - 1;
      SPI1U1 = (repeat << SPILMOSI);
      SPI1CMD |= SPIBUSY;
      while(SPI1CMD & SPIBUSY) {}
    }
  
  }
  
  #elif defined (ILI9488_DRIVER)
  
  #ifdef ESP8266
  void writeBlock(uint16_t color, uint32_t repeat)
  {
  
    // Split out the colours
    uint8_t r = (color & 0xF800)>>8;
    uint8_t g = (color & 0x07E0)>>3;
    uint8_t b = (color & 0x001F)<<3;
    // Concatenate 4 pixels into three 32 bit blocks
    uint32_t r0 = r<<24 | b<<16 | g<<8 | r;
    uint32_t r1 = g<<24 | r<<16 | b<<8 | g;
    uint32_t r2 = b<<24 | g<<16 | r<<8 | b;
  
    SPI1W0 = r0;
    SPI1W1 = r1;
    SPI1W2 = r2;
  
    if (repeat > 4)
    {
      SPI1W3 = r0;
      SPI1W4 = r1;
      SPI1W5 = r2;
    }
    if (repeat > 8)
    {
      SPI1W6 = r0;
      SPI1W7 = r1;
      SPI1W8 = r2;
    }
    if (repeat > 12)
    {
      SPI1W9  = r0;
      SPI1W10 = r1;
      SPI1W11 = r2;
      SPI1W12 = r0;
      SPI1W13 = r1;
      SPI1W14 = r2;
      SPI1W15 = r0;
    }
  
    if (repeat > 20)
    {
      SPI1U1 = (503 << SPILMOSI);
      while(repeat>20)
      {
        while(SPI1CMD & SPIBUSY) {}
        SPI1CMD |= SPIBUSY;
        repeat -= 21;
      }
      while(SPI1CMD & SPIBUSY) {}
    }
  
    if (repeat)
    {
      repeat = (repeat * 24) - 1;
      SPI1U1 = (repeat << SPILMOSI);
      SPI1CMD |= SPIBUSY;
      while(SPI1CMD & SPIBUSY) {}
    }
  
  }
  #else // Now the code for ESP32 and ILI9488
  
  void writeBlock(uint16_t color, uint32_t repeat)
  {
    // Split out the colours
    uint32_t r = (color & 0xF800)>>8;
    uint32_t g = (color & 0x07E0)<<5;
    uint32_t b = (color & 0x001F)<<19;
    // Concatenate 4 pixels into three 32 bit blocks
    uint32_t r0 = r<<24 | b | g | r;
    uint32_t r1 = r0>>8 | g<<16;
    uint32_t r2 = r1>>8 | b<<8;
  
    if (repeat > 19)
    {
      SET_PERI_REG_BITS(SPI_MOSI_DLEN_REG(SPI_PORT), SPI_USR_MOSI_DBITLEN, 479, SPI_USR_MOSI_DBITLEN_S);
  
      while(repeat>19)
      {
        while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
        WRITE_PERI_REG(SPI_W0_REG(SPI_PORT), r0);
        WRITE_PERI_REG(SPI_W1_REG(SPI_PORT), r1);
        WRITE_PERI_REG(SPI_W2_REG(SPI_PORT), r2);
        WRITE_PERI_REG(SPI_W3_REG(SPI_PORT), r0);
        WRITE_PERI_REG(SPI_W4_REG(SPI_PORT), r1);
        WRITE_PERI_REG(SPI_W5_REG(SPI_PORT), r2);
        WRITE_PERI_REG(SPI_W6_REG(SPI_PORT), r0);
        WRITE_PERI_REG(SPI_W7_REG(SPI_PORT), r1);
        WRITE_PERI_REG(SPI_W8_REG(SPI_PORT), r2);
        WRITE_PERI_REG(SPI_W9_REG(SPI_PORT), r0);
        WRITE_PERI_REG(SPI_W10_REG(SPI_PORT), r1);
        WRITE_PERI_REG(SPI_W11_REG(SPI_PORT), r2);
        WRITE_PERI_REG(SPI_W12_REG(SPI_PORT), r0);
        WRITE_PERI_REG(SPI_W13_REG(SPI_PORT), r1);
        WRITE_PERI_REG(SPI_W14_REG(SPI_PORT), r2);
        SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
        repeat -= 20;
      }
      while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
    }
  
    if (repeat)
    {
      SET_PERI_REG_BITS(SPI_MOSI_DLEN_REG(SPI_PORT), SPI_USR_MOSI_DBITLEN, (repeat * 24) - 1, SPI_USR_MOSI_DBITLEN_S);
      WRITE_PERI_REG(SPI_W0_REG(SPI_PORT), r0);
      WRITE_PERI_REG(SPI_W1_REG(SPI_PORT), r1);
      WRITE_PERI_REG(SPI_W2_REG(SPI_PORT), r2);
      WRITE_PERI_REG(SPI_W3_REG(SPI_PORT), r0);
      WRITE_PERI_REG(SPI_W4_REG(SPI_PORT), r1);
      WRITE_PERI_REG(SPI_W5_REG(SPI_PORT), r2);
      if (repeat > 8 )
      {
        WRITE_PERI_REG(SPI_W6_REG(SPI_PORT), r0);
        WRITE_PERI_REG(SPI_W7_REG(SPI_PORT), r1);
        WRITE_PERI_REG(SPI_W8_REG(SPI_PORT), r2);
        WRITE_PERI_REG(SPI_W9_REG(SPI_PORT), r0);
        WRITE_PERI_REG(SPI_W10_REG(SPI_PORT), r1);
        WRITE_PERI_REG(SPI_W11_REG(SPI_PORT), r2);
        WRITE_PERI_REG(SPI_W12_REG(SPI_PORT), r0);
        WRITE_PERI_REG(SPI_W13_REG(SPI_PORT), r1);
        WRITE_PERI_REG(SPI_W14_REG(SPI_PORT), r2);
      }
  
      SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
      while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
    }
  
  }
  #endif
  
  #else // Low level register based ESP32 code for 16 bit colour SPI TFTs
  
  void writeBlock(uint16_t color, uint32_t repeat)
  {
    uint32_t color32 = COL_32(color, color);
  
    if (repeat > 31) // Revert legacy toggle buffer change
    {
      WRITE_PERI_REG(SPI_MOSI_DLEN_REG(SPI_PORT), 511);
      while(repeat>31)
      {
        while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
        WRITE_PERI_REG(SPI_W0_REG(SPI_PORT), color32);
        WRITE_PERI_REG(SPI_W1_REG(SPI_PORT), color32);
        WRITE_PERI_REG(SPI_W2_REG(SPI_PORT), color32);
        WRITE_PERI_REG(SPI_W3_REG(SPI_PORT), color32);
        WRITE_PERI_REG(SPI_W4_REG(SPI_PORT), color32);
        WRITE_PERI_REG(SPI_W5_REG(SPI_PORT), color32);
        WRITE_PERI_REG(SPI_W6_REG(SPI_PORT), color32);
        WRITE_PERI_REG(SPI_W7_REG(SPI_PORT), color32);
        WRITE_PERI_REG(SPI_W8_REG(SPI_PORT), color32);
        WRITE_PERI_REG(SPI_W9_REG(SPI_PORT), color32);
        WRITE_PERI_REG(SPI_W10_REG(SPI_PORT), color32);
        WRITE_PERI_REG(SPI_W11_REG(SPI_PORT), color32);
        WRITE_PERI_REG(SPI_W12_REG(SPI_PORT), color32);
        WRITE_PERI_REG(SPI_W13_REG(SPI_PORT), color32);
        WRITE_PERI_REG(SPI_W14_REG(SPI_PORT), color32);
        WRITE_PERI_REG(SPI_W15_REG(SPI_PORT), color32);
        SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
        repeat -= 32;
      }
      while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
    }
  
    if (repeat)
    {
      // Revert toggle buffer change
      WRITE_PERI_REG(SPI_MOSI_DLEN_REG(SPI_PORT), (repeat << 4) - 1);
      for (uint32_t i=0; i <= (repeat>>1); i++) WRITE_PERI_REG((SPI_W0_REG(SPI_PORT) + (i << 2)), color32);
      SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
      while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
    }
  }
  #endif
  
  
  /***************************************************************************************
  ** Function name:           getSPIinstance
  ** Description:             Get the instance of the SPI class (for ESP32 only)
  ***************************************************************************************/
  #ifndef ESP32_PARALLEL
  SPIClass& TFT_eSPI::getSPIinstance(void)
  {
    return spi;
  }
  #endif
  
  
  /***************************************************************************************
  ** Function name:           getSetup
  ** Description:             Get the setup details for diagnostic and sketch access
  ***************************************************************************************/
  void TFT_eSPI::getSetup(setup_t &tft_settings)
  {
  // tft_settings.version is set in header file
  
  #if defined (ESP8266)
    tft_settings.esp = 8266;
  #elif defined (ESP32)
    tft_settings.esp = 32;
  #else
    tft_settings.esp = -1;
  #endif
  
  #if defined (SUPPORT_TRANSACTIONS)
    tft_settings.trans = true;
  #else
    tft_settings.trans = false;
  #endif
  
  #if defined (ESP32_PARALLEL)
    tft_settings.serial = false;
    tft_settings.tft_spi_freq = 0;
  #else
    tft_settings.serial = true;
    tft_settings.tft_spi_freq = SPI_FREQUENCY/100000;
    #ifdef SPI_READ_FREQUENCY
      tft_settings.tft_rd_freq = SPI_READ_FREQUENCY/100000;
    #endif
  #endif
  
  #if defined(TFT_SPI_OVERLAP)
    tft_settings.overlap = true;
  #else
    tft_settings.overlap = false;
  #endif
  
    tft_settings.tft_driver = TFT_DRIVER;
    tft_settings.tft_width  = _init_width;
    tft_settings.tft_height = _init_height;
  
  #ifdef CGRAM_OFFSET
    tft_settings.r0_x_offset = colstart;
    tft_settings.r0_y_offset = rowstart;
    tft_settings.r1_x_offset = 0;
    tft_settings.r1_y_offset = 0;
    tft_settings.r2_x_offset = 0;
    tft_settings.r2_y_offset = 0;
    tft_settings.r3_x_offset = 0;
    tft_settings.r3_y_offset = 0;
  #else
    tft_settings.r0_x_offset = 0;
    tft_settings.r0_y_offset = 0;
    tft_settings.r1_x_offset = 0;
    tft_settings.r1_y_offset = 0;
    tft_settings.r2_x_offset = 0;
    tft_settings.r2_y_offset = 0;
    tft_settings.r3_x_offset = 0;
    tft_settings.r3_y_offset = 0;
  #endif
  
  #if defined (TFT_MOSI)
    tft_settings.pin_tft_mosi = TFT_MOSI;
  #else
    tft_settings.pin_tft_mosi = -1;
  #endif
  
  #if defined (TFT_MISO)
    tft_settings.pin_tft_miso = TFT_MISO;
  #else
    tft_settings.pin_tft_miso = -1;
  #endif
  
  #if defined (TFT_SCLK)
    tft_settings.pin_tft_clk  = TFT_SCLK;
  #else
    tft_settings.pin_tft_clk  = -1;
  #endif
  
  #if defined (TFT_CS)
    tft_settings.pin_tft_cs   = TFT_CS;
  #else
    tft_settings.pin_tft_cs   = -1;
  #endif
  
  #if defined (TFT_DC)
    tft_settings.pin_tft_dc  = TFT_DC;
  #else
    tft_settings.pin_tft_dc  = -1;
  #endif
  
  #if defined (TFT_RD)
    tft_settings.pin_tft_rd  = TFT_RD;
  #else
    tft_settings.pin_tft_rd  = -1;
  #endif
  
  #if defined (TFT_WR)
    tft_settings.pin_tft_wr  = TFT_WR;
  #else
    tft_settings.pin_tft_wr  = -1;
  #endif
  
  #if defined (TFT_RST)
    tft_settings.pin_tft_rst = TFT_RST;
  #else
    tft_settings.pin_tft_rst = -1;
  #endif
  
  #if defined (ESP32_PARALLEL)
    tft_settings.pin_tft_d0 = TFT_D0;
    tft_settings.pin_tft_d1 = TFT_D1;
    tft_settings.pin_tft_d2 = TFT_D2;
    tft_settings.pin_tft_d3 = TFT_D3;
    tft_settings.pin_tft_d4 = TFT_D4;
    tft_settings.pin_tft_d5 = TFT_D5;
    tft_settings.pin_tft_d6 = TFT_D6;
    tft_settings.pin_tft_d7 = TFT_D7;
  #else
    tft_settings.pin_tft_d0 = -1;
    tft_settings.pin_tft_d1 = -1;
    tft_settings.pin_tft_d2 = -1;
    tft_settings.pin_tft_d3 = -1;
    tft_settings.pin_tft_d4 = -1;
    tft_settings.pin_tft_d5 = -1;
    tft_settings.pin_tft_d6 = -1;
    tft_settings.pin_tft_d7 = -1;
  #endif
  
  #if defined (TOUCH_CS)
    tft_settings.pin_tch_cs   = TOUCH_CS;
    tft_settings.tch_spi_freq = SPI_TOUCH_FREQUENCY/100000;
  #else
    tft_settings.pin_tch_cs   = -1;
    tft_settings.tch_spi_freq = 0;
  #endif
  }
  
  ////////////////////////////////////////////////////////////////////////////////////////
  #ifdef TOUCH_CS
    #include "Extensions/Touch.cpp"
    #include "Extensions/Button.cpp"
  #endif
  
  // for M5.Lcd comment out.
  // #include "Extensions/Sprite.cpp"
  
  #ifdef SMOOTH_FONT
  //  #include "Extensions/Smooth_font.cpp"
   // Coded by Bodmer 10/2/18, see license in root directory.
   // This is part of the TFT_eSPI class and is associated with anti-aliased font functions
   
  
  ////////////////////////////////////////////////////////////////////////////////////////
  // New anti-aliased (smoothed) font functions added below
  ////////////////////////////////////////////////////////////////////////////////////////
  
  /***************************************************************************************
  ** Function name:           loadFont
  ** Description:             loads parameters from a new font vlw file
  *************************************************************************************x*/
  void TFT_eSPI::loadFont(String fontName, fs::FS &ffs)
  {
    fontFS = ffs;
    loadFont(fontName, false);
  }
  /***************************************************************************************
  ** Function name:           loadFont
  ** Description:             loads parameters from a new font vlw file
  *************************************************************************************x*/
  void TFT_eSPI::loadFont(String fontName, bool flash)
  {
    /*
      The vlw font format does not appear to be documented anywhere, so some reverse
      engineering has been applied!
  
      Header of vlw file comprises 6 uint32_t parameters (24 bytes total):
        1. The gCount (number of character glyphs)
        2. A version number (0xB = 11 for the one I am using)
        3. The font size (in points, not pixels)
        4. Deprecated mboxY parameter (typically set to 0)
        5. Ascent in pixels from baseline to top of "d"
        6. Descent in pixels from baseline to bottom of "p"
  
      Next are gCount sets of values for each glyph, each set comprises 7 int32t parameters (28 bytes):
        1. Glyph Unicode stored as a 32 bit value
        2. Height of bitmap bounding box
        3. Width of bitmap bounding box
        4. gxAdvance for cursor (setWidth in Processing)
        5. dY = distance from cursor baseline to top of glyph bitmap (signed value +ve = up)
        6. dX = distance from cursor to left side of glyph bitmap (signed value -ve = left)
        7. padding value, typically 0
  
      The bitmaps start next at 24 + (28 * gCount) bytes from the start of the file.
      Each pixel is 1 byte, an 8 bit Alpha value which represents the transparency from
      0xFF foreground colour, 0x00 background. The sketch uses a linear interpolation
      between the foreground and background RGB component colours. e.g.
          pixelRed = ((fgRed * alpha) + (bgRed * (255 - alpha))/255
      To gain a performance advantage fixed point arithmetic is used with rounding and
      division by 256 (shift right 8 bits is faster).
  
      After the bitmaps is:
         1 byte for font name string length (excludes null)
         a zero terminated character string giving the font name
         1 byte for Postscript name string length
         a zero/one terminated character string giving the font name
         last byte is 0 for non-anti-aliased and 1 for anti-aliased (smoothed)
  
      Then the font name seen by Java when it's created
      Then the postscript name of the font
      Then a boolean to tell if smoothing is on or not.
  
      Glyph bitmap example is:
      // Cursor coordinate positions for this and next character are marked by 'C'
      // C<------- gxAdvance ------->C  gxAdvance is how far to move cursor for next glyph cursor position
      // |                           |
      // |                           |   ascent is top of "d", descent is bottom of "p"
      // +-- gdX --+             ascent
      // |         +-- gWidth--+     |   gdX is offset to left edge of glyph bitmap
      // |   +     x@.........@x  +  |   gdX may be negative e.g. italic "y" tail extending to left of
      // |   |     @@.........@@  |  |   cursor position, plot top left corner of bitmap at (cursorX + gdX)
      // |   |     @@.........@@ gdY |   gWidth and gHeight are glyph bitmap dimensions
      // |   |     .@@@.....@@@@  |  |
      // | gHeight ....@@@@@..@@  +  +    <-- baseline
      // |   |     ...........@@     |
      // |   |     ...........@@     |   gdY is the offset to the top edge of the bitmap
      // |   |     .@@.......@@. descent plot top edge of bitmap at (cursorY + yAdvance - gdY)
      // |   +     x..@@@@@@@..x     |   x marks the corner pixels of the bitmap
      // |                           |
      // +---------------------------+   yAdvance is y delta for the next line, font size or (ascent + descent)
      //                                  some fonts can overlay in y direction so may need a user adjust value
  
    */
  
    spiffs = flash;
  
    if(spiffs) fontFS = SPIFFS;
  
    unloadFont();
  
    // Avoid a crash on the ESP32 if the file does not exist
    if (fontFS.exists("/" + fontName + ".vlw") == false) {
      Serial.println("Font file " + fontName + " not found!");
      return;
    }
  
    fontFile = fontFS.open( "/" + fontName + ".vlw", "r");
  
    if(!fontFile) return;
  
    fontFile.seek(0, fs::SeekSet);
  
    gFont.gCount   = (uint16_t)readInt32(); // glyph count in file
                               readInt32(); // vlw encoder version - discard
    gFont.yAdvance = (uint16_t)readInt32(); // Font size in points, not pixels
                               readInt32(); // discard
    gFont.ascent   = (uint16_t)readInt32(); // top of "d"
    gFont.descent  = (uint16_t)readInt32(); // bottom of "p"
  
    // These next gFont values might be updated when the Metrics are fetched
    gFont.maxAscent  = gFont.ascent;   // Determined from metrics
    gFont.maxDescent = gFont.descent;  // Determined from metrics
    gFont.yAdvance   = gFont.ascent + gFont.descent;
    gFont.spaceWidth = gFont.yAdvance / 4;  // Guess at space width
  
    fontLoaded = true;
  
    // Fetch the metrics for each glyph
    loadMetrics(gFont.gCount);
  
    //fontFile.close();
  }
  
  
  /***************************************************************************************
  ** Function name:           loadMetrics
  ** Description:             Get the metrics for each glyph and store in RAM
  *************************************************************************************x*/
  //#define SHOW_ASCENT_DESCENT
  void TFT_eSPI::loadMetrics(uint16_t gCount)
  {
    uint32_t headerPtr = 24;
    uint32_t bitmapPtr = 24 + gCount * 28;
  
  #if defined (ESP32) && defined (CONFIG_SPIRAM_SUPPORT)
    if ( psramFound() )
    {
      gUnicode  = (uint16_t*)ps_malloc( gCount * 2); // Unicode 16 bit Basic Multilingual Plane (0-FFFF)
      gHeight   =  (uint8_t*)ps_malloc( gCount );    // Height of glyph
      gWidth    =  (uint8_t*)ps_malloc( gCount );    // Width of glyph
      gxAdvance =  (uint8_t*)ps_malloc( gCount );    // xAdvance - to move x cursor
      gdY       =  (int16_t*)ps_malloc( gCount * 2); // offset from bitmap top edge from lowest point in any character
      gdX       =   (int8_t*)ps_malloc( gCount );    // offset for bitmap left edge relative to cursor X
      gBitmap   = (uint32_t*)ps_malloc( gCount * 4); // seek pointer to glyph bitmap in the file
    }
    else
  #endif
    {
      gUnicode  = (uint16_t*)malloc( gCount * 2); // Unicode 16 bit Basic Multilingual Plane (0-FFFF)
      gHeight   =  (uint8_t*)malloc( gCount );    // Height of glyph
      gWidth    =  (uint8_t*)malloc( gCount );    // Width of glyph
      gxAdvance =  (uint8_t*)malloc( gCount );    // xAdvance - to move x cursor
      gdY       =  (int16_t*)malloc( gCount * 2); // offset from bitmap top edge from lowest point in any character
      gdX       =   (int8_t*)malloc( gCount );    // offset for bitmap left edge relative to cursor X
      gBitmap   = (uint32_t*)malloc( gCount * 4); // seek pointer to glyph bitmap in the file
    }
  
  #ifdef SHOW_ASCENT_DESCENT
    Serial.print("ascent  = "); Serial.println(gFont.ascent);
    Serial.print("descent = "); Serial.println(gFont.descent);
  #endif
  
    uint16_t gNum = 0;
    fontFile.seek(headerPtr, fs::SeekSet);
    while (gNum < gCount)
    {
      gUnicode[gNum]  = (uint16_t)readInt32(); // Unicode code point value
      gHeight[gNum]   =  (uint8_t)readInt32(); // Height of glyph
      gWidth[gNum]    =  (uint8_t)readInt32(); // Width of glyph
      gxAdvance[gNum] =  (uint8_t)readInt32(); // xAdvance - to move x cursor
      gdY[gNum]       =  (int16_t)readInt32(); // y delta from baseline
      gdX[gNum]       =   (int8_t)readInt32(); // x delta from cursor
      readInt32(); // ignored
  
      //Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", gHeight  = "); Serial.println(gHeight[gNum]);
      //Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", gWidth  = "); Serial.println(gWidth[gNum]);
      //Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", gxAdvance  = "); Serial.println(gxAdvance[gNum]);
      //Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", gdY  = "); Serial.println(gdY[gNum]);
  
      // Different glyph sets have different ascent values not always based on "d", so we could get
      // the maximum glyph ascent by checking all characters. BUT this method can generate bad values
      // for non-existant glyphs, so we will reply on processing for the value and disable this code for now...
      /*
      if (gdY[gNum] > gFont.maxAscent)
      {
        // Try to avoid UTF coding values and characters that tend to give duff values
        if (((gUnicode[gNum] > 0x20) && (gUnicode[gNum] < 0x7F)) || (gUnicode[gNum] > 0xA0))
        {
          gFont.maxAscent   = gdY[gNum];
  #ifdef SHOW_ASCENT_DESCENT
          Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", maxAscent  = "); Serial.println(gFont.maxAscent);
  #endif
        }
      }
      */
  
      // Different glyph sets have different descent values not always based on "p", so get maximum glyph descent
      if (((int16_t)gHeight[gNum] - (int16_t)gdY[gNum]) > gFont.maxDescent)
      {
        // Avoid UTF coding values and characters that tend to give duff values
        if (((gUnicode[gNum] > 0x20) && (gUnicode[gNum] < 0xA0) && (gUnicode[gNum] != 0x7F)) || (gUnicode[gNum] > 0xFF))
        {
          gFont.maxDescent   = gHeight[gNum] - gdY[gNum];
  #ifdef SHOW_ASCENT_DESCENT
          Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", maxDescent = "); Serial.println(gHeight[gNum] - gdY[gNum]);
  #endif
        }
      }
  
      gBitmap[gNum] = bitmapPtr;
  
      headerPtr += 28;
  
      bitmapPtr += gWidth[gNum] * gHeight[gNum];
  
      gNum++;
      yield();
    }
  
    gFont.yAdvance = gFont.maxAscent + gFont.maxDescent;
  
    gFont.spaceWidth = (gFont.ascent + gFont.descent) * 2/7;  // Guess at space width
  }
  
  
  /***************************************************************************************
  ** Function name:           deleteMetrics
  ** Description:             Delete the old glyph metrics and free up the memory
  *************************************************************************************x*/
  void TFT_eSPI::unloadFont( void )
  {
    if (gUnicode)
    {
      free(gUnicode);
      gUnicode = NULL;
    }
  
    if (gHeight)
    {
      free(gHeight);
      gHeight = NULL;
    }
  
    if (gWidth)
    {
      free(gWidth);
      gWidth = NULL;
    }
  
    if (gxAdvance)
    {
      free(gxAdvance);
      gxAdvance = NULL;
    }
  
    if (gdY)
    {
      free(gdY);
      gdY = NULL;
    }
  
    if (gdX)
    {
      free(gdX);
      gdX = NULL;
    }
  
    if (gBitmap)
    {
      free(gBitmap);
      gBitmap = NULL;
    }
  
    if(fontFile) fontFile.close();
    fontLoaded = false;
  }
  
  
  /***************************************************************************************
  ** Function name:           decodeUTF8
  ** Description:             Line buffer UTF-8 decoder with fall-back to extended ASCII
  *************************************************************************************x*/
  /* Function moved to TFT_eSPI.cpp
  #define DECODE_UTF8
  uint16_t TFT_eSPI::decodeUTF8(uint8_t *buf, uint16_t *index, uint16_t remaining)
  {
    byte c = buf[(*index)++];
    //Serial.print("Byte from string = 0x"); Serial.println(c, HEX);
  
  #ifdef DECODE_UTF8
    // 7 bit Unicode
    if ((c & 0x80) == 0x00) return c;
  
    // 11 bit Unicode
    if (((c & 0xE0) == 0xC0) && (remaining > 1))
      return ((c & 0x1F)<<6) | (buf[(*index)++]&0x3F);
  
    // 16 bit Unicode
    if (((c & 0xF0) == 0xE0) && (remaining > 2))
    {
      c = ((c & 0x0F)<<12) | ((buf[(*index)++]&0x3F)<<6);
      return  c | ((buf[(*index)++]&0x3F));
    }
  
    // 21 bit Unicode not supported so fall-back to extended ASCII
    // if ((c & 0xF8) == 0xF0) return c;
  #endif
  
    return c; // fall-back to extended ASCII
  }
  */
  
  /***************************************************************************************
  ** Function name:           decodeUTF8
  ** Description:             *************************************************************************************x*/
  /* Function moved to TFT_eSPI.cpp
  uint16_t TFT_eSPI::decodeUTF8(uint8_t c)
  {
  
  #ifdef DECODE_UTF8
  
    // 7 bit Unicode
    if ((c & 0x80) == 0x00) {
      decoderState = 0;
      return (uint16_t)c;
    }
  
    if (decoderState == 0)
    {
      // 11 bit Unicode
      if ((c & 0xE0) == 0xC0)
      {
        decoderBuffer = ((c & 0x1F)<<6);
        decoderState = 1;
        return 0;
      }
  
      // 16 bit Unicode
      if ((c & 0xF0) == 0xE0)
      {
        decoderBuffer = ((c & 0x0F)<<12);
        decoderState = 2;
        return 0;
      }
      // 21 bit Unicode not supported so fall-back to extended ASCII
      if ((c & 0xF8) == 0xF0) return (uint16_t)c;
    }
    else
    {
      if (decoderState == 2)
      {
        decoderBuffer |= ((c & 0x3F)<<6);
        decoderState--;
        return 0;
      }
      else
      {
        decoderBuffer |= (c & 0x3F);
        decoderState = 0;
        return decoderBuffer;
      }
    }
  #endif
  
    decoderState = 0;
    return (uint16_t)c; // fall-back to extended ASCII
  }
  */
  
  
  /***************************************************************************************
  ** Function name:           alphaBlend
  ** Description:             Blend foreground and background and return new colour
  *************************************************************************************x*/
  uint16_t TFT_eSPI::alphaBlend(uint8_t alpha, uint16_t fgc, uint16_t bgc)
  {
    if(alpha == 255)
    {
      return fgc;
    }
    else if(alpha == 0)
    {
      return bgc;
    }
    if(fgc == bgc)
    {
      return fgc;
    }
    // For speed use fixed point maths and rounding to permit a power of 2 division
    uint16_t fgR = ((fgc >> 10) & 0x3E) + 1;
    uint16_t fgG = ((fgc >>  4) & 0x7E) + 1;
    uint16_t fgB = ((fgc <<  1) & 0x3E) + 1;
  
    uint16_t bgR = ((bgc >> 10) & 0x3E) + 1;
    uint16_t bgG = ((bgc >>  4) & 0x7E) + 1;
    uint16_t bgB = ((bgc <<  1) & 0x3E) + 1;
  
    // Shift right 1 to drop rounding bit and shift right 8 to divide by 256
    uint16_t r = (((fgR * alpha) + (bgR * (255 - alpha))) >> 9);
    uint16_t g = (((fgG * alpha) + (bgG * (255 - alpha))) >> 9);
    uint16_t b = (((fgB * alpha) + (bgB * (255 - alpha))) >> 9);
  
    // Combine RGB565 colours into 16 bits
    //return ((r&0x18) << 11) | ((g&0x30) << 5) | ((b&0x18) << 0); // 2 bit greyscale
    //return ((r&0x1E) << 11) | ((g&0x3C) << 5) | ((b&0x1E) << 0); // 4 bit greyscale
    return (r << 11) | (g << 5) | (b << 0);
  }
  
  
  /***************************************************************************************
  ** Function name:           readInt32
  ** Description:             Get a 32 bit integer from the font file
  *************************************************************************************x*/
  uint32_t TFT_eSPI::readInt32(void)
  {
    uint32_t val = 0;
    val |= fontFile.read() << 24;
    val |= fontFile.read() << 16;
    val |= fontFile.read() << 8;
    val |= fontFile.read();
    return val;
  }
  
  
  /***************************************************************************************
  ** Function name:           getUnicodeIndex
  ** Description:             Get the font file index of a Unicode character
  *************************************************************************************x*/
  bool TFT_eSPI::getUnicodeIndex(uint16_t unicode, uint16_t *index)
  {
    for (uint16_t i = 0; i < gFont.gCount; i++)
    {
      if (gUnicode[i] == unicode)
      {
        *index = i;
        return true;
      }
    }
    return false;
  }
  
  
  /***************************************************************************************
  ** Function name:           drawGlyph
  ** Description:             Write a character to the TFT cursor position
  *************************************************************************************x*/
  // Expects file to be openrange_pst
  void TFT_eSPI::drawGlyph(uint16_t code)
  {
    if (code < 0x21)
    {
      if (code == 0x20) {
        cursor_x += gFont.spaceWidth;
        return;
      }
  
      if (code == '\n') {
        cursor_x = 0;
        cursor_y += gFont.yAdvance;
        if (cursor_y >= _height) cursor_y = 0;
        return;
      }
    }
  
    uint16_t gNum = 0;
    bool found = getUnicodeIndex(code, &gNum);
    
    uint16_t fg = textcolor;
    uint16_t bg = textbgcolor;
  
    if (found)
    {
  
      if (textwrapX && (cursor_x + gWidth[gNum] + gdX[gNum] > _width))
      {
        cursor_y += gFont.yAdvance;
        cursor_x = 0;
      }
      if (textwrapY && ((cursor_y + gFont.yAdvance) >= _height)) cursor_y = 0;
      if (cursor_x == 0) cursor_x -= gdX[gNum];
  
      fontFile.seek(gBitmap[gNum], fs::SeekSet); // This is taking >30ms for a significant position shift
  
      uint8_t pbuffer[gWidth[gNum]];
  
      int16_t xs = 0;
      uint32_t dl = 0;
  
      int16_t cy = cursor_y + gFont.maxAscent - gdY[gNum];
      int16_t cx = cursor_x + gdX[gNum];
  
      startWrite(); // Avoid slow ESP32 transaction overhead for every pixel
  
      for (int y = 0; y < gHeight[gNum]; y++)
      {
        if (spiffs)
        {
          fontFile.read(pbuffer, gWidth[gNum]);
          //Serial.println("SPIFFS");
        }
        else
        {
          endWrite();    // Release SPI for SD card transaction
          fontFile.read(pbuffer, gWidth[gNum]);
          startWrite();  // Re-start SPI for TFT transaction
          //Serial.println("Not SPIFFS");
        }
  
        for (int x = 0; x < gWidth[gNum]; x++)
        {
          uint8_t pixel = pbuffer[x]; //<//
          if (pixel)
          {
            if (pixel != 0xFF)
            {
              if (dl) {
                if (dl==1) drawPixel(xs, y + cy, fg);
                else drawFastHLine( xs, y + cy, dl, fg);
                dl = 0;
              }
              if (getColor) bg = getColor(x + cx, y + cy);
              drawPixel(x + cx, y + cy, alphaBlend(pixel, fg, bg));
            }
            else
            {
              if (dl==0) xs = x + cx;
              dl++;
            }
          }
          else
          {
            if (dl) { drawFastHLine( xs, y + cy, dl, fg); dl = 0; }
          }
        }
        if (dl) { drawFastHLine( xs, y + cy, dl, fg); dl = 0; }
      }
  
      cursor_x += gxAdvance[gNum];
      endWrite();
    }
    else
    {
      // Not a Unicode in font so draw a rectangle and move on cursor
      drawRect(cursor_x, cursor_y + gFont.maxAscent - gFont.ascent, gFont.spaceWidth, gFont.ascent, fg);
      cursor_x += gFont.spaceWidth + 1;
    }
  }
  
  /***************************************************************************************
  ** Function name:           showFont
  ** Description:             Page through all characters in font, td ms between screens
  *************************************************************************************x*/
  void TFT_eSPI::showFont(uint32_t td)
  {
    if(!fontLoaded) return;
  
    if(!fontFile)
    {
      fontLoaded = false;
      return;
    }
  
    int16_t cursorX = width(); // Force start of new page to initialise cursor
    int16_t cursorY = height();// for the first character
    uint32_t timeDelay = 0;    // No delay before first page
  
    fillScreen(textbgcolor);
    
    for (uint16_t i = 0; i < gFont.gCount; i++)
    {
      // Check if this will need a new screen
      if (cursorX + gdX[i] + gWidth[i] >= width())  {
        cursorX = -gdX[i];
  
        cursorY += gFont.yAdvance;
        if (cursorY + gFont.maxAscent + gFont.descent >= height()) {
          cursorX = -gdX[i];
          cursorY = 0;
          delay(timeDelay);
          timeDelay = td;
          fillScreen(textbgcolor);
        }
      }
  
      setCursor(cursorX, cursorY);
      drawGlyph(gUnicode[i]);
      cursorX += gxAdvance[i];
      //cursorX +=  printToSprite( cursorX, cursorY, i );
      yield();
    }
  
    delay(timeDelay);
    fillScreen(textbgcolor);
    //fontFile.close();
  
  }
  #endif
  
  #ifdef USE_M5_FONT_CREATOR
  /***************************************************************************************
  ** Function name:           showFont
  ** Description:             Page through all characters in font, td ms between screens
  *************************************************************************************x*/
  int32_t TFT_eSPI::getUnicodeFontIndex(uint32_t unicode) {
    int32_t index = -1;
    EncodeRange *range_pst = (EncodeRange *)pgm_read_dword(&gfxFont->range);
    uint16_t custom_range_num = pgm_read_word(&gfxFont->range_num);
  
    if(custom_range_num != 0) {
      if ((unicode >= pgm_read_word(&range_pst[0].start)) && (unicode <= pgm_read_word(&range_pst[0].end))) {
        index = unicode - pgm_read_word(&range_pst[0].start) + pgm_read_word(&range_pst[0].base);
      } else {
        int32_t right = pgm_read_word(&gfxFont->range_num);
        int32_t left = 0;
        int32_t middle = 0;
        while (left <= right) {
          middle = (right + left) >> 1;
          if (unicode > pgm_read_word(&range_pst[middle].end)) {
            left = middle + 1;
          } else if (unicode < pgm_read_word(&range_pst[middle].start)) {
            right = middle - 1;
          } else if ((unicode >= pgm_read_word(&range_pst[middle].start)) && (unicode <= pgm_read_word(&range_pst[middle].end))) {
            index = unicode - pgm_read_word(&range_pst[middle].start) + pgm_read_word(&range_pst[middle].base);
            break;
          }
        }
      }
    } else {
      if ((unicode >= pgm_read_word(&gfxFont->first)) && (unicode <= pgm_read_word(&gfxFont->last))) {
        index = unicode - pgm_read_word(&gfxFont->first);
      }
    }
  
    return index;
  }
  
  #endif
  ////////////////////////////////////////////////////////////////////////////////////////
  