'======================================================================================
' File:       Max7219SPI_Lib.bas
' Target:     PIC18F46K22  (MSSP1 used as SPI-like synchronous serial)
' Toolchain:  PIC18 Basic Compiler V5.91 (Oshonsoft), PCB HL-K18
' By COS, (dogflu66@yahoo.es) 2024/09/02, 2024/06
'
' Updated:    2025-08-15 (This latest update has been enhanced with AI assistance.
'			  Therefore, there may be unintentional matches with other codes).
'**************************************************************************************
' Important: Options menu -> Initialize variables in declaration -> not selected.
' Include "SPI_Lib.bas"
' --------
' Updated (2025/08/15):
' This library drives a daisychained line of MAX7219 LED driver ICs configured as
' 8x8 matrix modules. It implements a byte-addressable framebuffer and generic send
' routines that scale to an arbitrary number of matrices (set by MAX7219_MATRIX_COUNT).
' ---------
' MAX7219 Serial Protocol (SPI-like)
' ----------------------------------
' - Lines: DIN (data-in), CLK (clock), LOAD/CS (latch).
' - Transactions are 16 bits: [REGISTER(8)] then [DATA(8)], MSB-first.
' - LOAD/CS should be asserted low for the whole burst and released high to latch.
' - The first [REG,DATA] pair shifted out ends up in the *furthest* device in the chain
'   (left-most by convention); the last pair is latched by the device closest to the MCU.
' - MAX7219 is *SPI-like* but not strictly SPI-compliant (MAX7221 is). Standard SPI mode 0
'   (CKP=0, CKE=1) works fine in practice on PIC MSSP.
'
' Framebuffer Model
' -----------------
' - FrameBuffer is organized as 8 rows x MAX7219_MATRIX_COUNT bytes per row.
' - MAX7219_WriteFrameBuffer_Generic() sends each row in order left-most .. right-most,
'   matching the daisy-chain shift order.
'
' Configuration
' -------------
' - Set MAX7219_MATRIX_COUNT to the number of matrices in your chain (>=1).
' - FrameBuffer is sized here to 32 bytes (8 rows x 4 matrices). If you chain more than
'   4 matrices, increase the array size to (8 * MAX7219_MATRIX_COUNT).
'
' Public API (summary)
' --------------------
' - MAX7219_Init()                 : basic device setup + clear framebuffer
' - MAX7219_Bright(level 0..15)    : set global intensity
' - MAX7219_ClearMatrix()          : clear all rows (broadcast zeros)
' - LeftShift(addr, text$)         : smooth left scroll with 90° glyph injection
' - RightShift(addr, text$)        : smooth right scroll with 90° glyph injection
' - MAX7219_WriteFrameBuffer_Generic(addr) : push full frame to the chain
'
' Integration
' -----------
' - Requires _SPI_StartWrite(), _SPI_Write(), _SPI_EndWrite() from SPI_Lib.bas.
' - Typical wiring (PIC18F46K22):
'       RC5 -> DIN (SDO/MOSI)
'       RC3 -> CLK (SCK)
'       RC2 -> CS  (user-defined CS)
' - Recommended SPI: Mode 0 (CKP=0, CKE=1), MSB-first.
'
' License
' -------
' - Use/modify at will. No warranty. Test on your hardware.
'======================================================================================

'Include "SPI_Lib.bas"

'======================================================================================
' Chain size and framebuffer
'======================================================================================
Const MAX7219_MATRIX_COUNT = 4          ' Set to your chain length (>=1)

' Buffer: 8 rows x MAX7219_MATRIX_COUNT bytes per row
'Dim FrameBuffer(8 * MAX7219_MATRIX_COUNT) As Byte  ' <- use this for >8 matrices
Dim FrameBuffer(32) As Byte                         ' 8 rows x 4 matrices (default)
Dim P_FrameBuffer As Word                           ' Base pointer for Pointer()

' Scroll state (persists across calls)
Dim Max7219_scrollX As Byte                         ' Pixel shift counter 0..7
Dim Max7219_scrollIdx As Byte                       ' Current char index in source text

'======================================================================================
' Pointer mapping (Pointers are used to access the FrameBuffer, thus simplifying the implementation of multiple character or symbol tables at the same time.)
'======================================================================================
' Pointer_Init
' Initializes P_FrameBuffer with the base address of FrameBuffer().
Function Pointer_Init() As Byte
    Dim address As Word
    Symbol address_LB = address.LB
    Symbol address_HB = address.HB
    ASM:        MOVLW Low FrameBuffer
    ASM:        MOVWF address_LB
    ASM:        MOVLW High FrameBuffer
    ASM:        MOVWF address_HB
    P_FrameBuffer = address
End Function

'======================================================================================
' Low-level MAX7219 helpers
'======================================================================================
' MAX7219_Send_Init
' Broadcasts a single [REG,DATA] pair to ALL devices (uniform configuration).
Function MAX7219_Send_Init(Command As Byte, data As Byte) As Byte
    Dim i As Byte

    Call _SPI_StartWrite()
    For i = 1 To MAX7219_MATRIX_COUNT
        Call _SPI_Write(Command)
        Call _SPI_Write(data)
    Next i
    Call _SPI_EndWrite()
End Function

' MAX7219_SendArray
' Sends one [REG,DATA] pair per device from a contiguous byte array.
' Data order in memory MUST be: left-most .. right-most.
Function MAX7219_SendArray(Command As Byte, AddressData As Word, Cnt As Byte) As Byte
    Dim i As Byte

    Call _SPI_StartWrite()
    For i = 0 To Cnt - 1
        Call _SPI_Write(Command)
        Call _SPI_Write(Pointer(AddressData + i))
    Next i
    Call _SPI_EndWrite()
End Function

' MAX7219_PushRow
' Efficiently pushes one row (RowReg = 1..8) to all devices, using left..right order.
Function MAX7219_PushRow(RowReg As Byte, AddressData As Word) As Byte
    Dim i As Byte

    Call _SPI_StartWrite()
    For i = 0 To MAX7219_MATRIX_COUNT - 1
        Call _SPI_Write(RowReg)
        Call _SPI_Write(Pointer(AddressData + i))
    Next i
    Call _SPI_EndWrite()
End Function

'======================================================================================
' Public API
'======================================================================================
' MAX7219_Init
' Basic device setup (no-decode, 8 rows, normal op, test off) and clear framebuffer.
Function MAX7219_Init() As Byte
    Dim i As Word

    Call Pointer_Init()

    For i = 0 To (8 * MAX7219_MATRIX_COUNT) - 1
        Pointer(P_FrameBuffer +i) = 0
    Next i

    Max7219_scrollX = 0
    Max7219_scrollIdx = 0

    Call MAX7219_Send_Init($09, $00)    ' Decode mode: no decode
    Call MAX7219_Send_Init($0A, $03)    ' Intensity: medium
    Call MAX7219_Send_Init($0B, $07)    ' Scan limit: 8 rows (0..7)
    Call MAX7219_Send_Init($0C, $01)    ' Shutdown: normal operation
    Call MAX7219_Send_Init($0F, $00)    ' Display test: off
End Function

' MAX7219_Bright
' Sets brightness (0..15). Values above 15 are clamped to 15.
Function MAX7219_Bright(bright As Byte) As Byte
    If bright > 15 Then
        bright = 15
    Endif
    Call MAX7219_Send_Init($0A, bright)
End Function

' MAX7219_ClearMatrix
' Clears all rows (broadcast zeros) across the whole chain.
Function MAX7219_ClearMatrix() As Byte
    Dim row As Byte
    For row = 1 To 8
        Call MAX7219_Send_Init(row, $00)
    Next row
End Function

'======================================================================================
' Framebuffer flush
'======================================================================================
' MAX7219_WriteFrameBuffer_Generic
' Sends the entire frame (8 rows). For each row, builds left..right order on the fly.
Function MAX7219_WriteFrameBuffer_Generic(AddressData As Word) As Byte
    Dim row As Byte
    Dim m As Byte
    Dim y As Word
    Dim tmp As Byte
    Dim addrRow As Word

    ' One StartWrite/EndWrite per row minimizes overhead on long chains.
    y = 0
    For row = 0 To 7
        Call _SPI_StartWrite()
        For m = 1 To MAX7219_MATRIX_COUNT
            tmp = Pointer(AddressData + y + (MAX7219_MATRIX_COUNT - m)) ' left-most first
            Call _SPI_Write(row + 1)                                    ' register 1..8
            Call _SPI_Write(tmp)                                        ' row byte
        Next m
        Call _SPI_EndWrite()
        y = y + MAX7219_MATRIX_COUNT
     Next row
End Function

'======================================================================================
' Pixel write & glyph blit (rotated)
'======================================================================================
' MAX7219_WritePixel
' Sets/clears one bit inside a FrameBuffer byte via Pointer().
' _pixelMatrix is the bit index (0..7) within that byte.
Function MAX7219_WritePixel(_AddrVRam As Word, _pixelMatrix As Byte, _pixelMask As Bit) As Byte
    Pointer(_AddrVRam)._pixelMatrix = _pixelMask
End Function

' MAX7219_RotateMatrix90
' Renders one glyph rotated 90° into a target matrix column group.
' Matrix index: 1 = right-most ... MAX7219_MATRIX_COUNT = left-most.
Function MAX7219_RotateMatrix90(AddressData As Word, char As Byte, Matrix As Byte) As Byte
    Dim baseOff As Word
    Dim mask As Byte
    Dim i As Byte
    Dim v As Byte
    Dim mr As Byte
    Dim off As Word

    v = 0
    baseOff = (MAX7219_MATRIX_COUNT - Matrix)

    For i = 7 To 0 Step -1
        mask = MAX7219_GlcdAscii(char, i)

        off = baseOff
        For mr = 0 To 7
            Call MAX7219_WritePixel(AddressData + off, v, mask.mr)
            off = off + MAX7219_MATRIX_COUNT
        Next mr

        v = v + 1
    Next i
End Function

'======================================================================================
' Smooth scrolling (left/right) with carry propagation across the row
'======================================================================================
' LeftShift
' Shifts the full width (8 * MAX7219_MATRIX_COUNT bits) LEFT by one per row.
' Every 8 shifts, injects the next glyph (rotated) into the left-most matrix.
Function LeftShift(AddressData As Word, _string As String) As Byte
    Dim i As Byte
    Dim j As Byte
    Dim y As Word
    Dim crry As Byte
    Dim b As Byte
    Dim _len As Byte

    _len = Len(_string)
    If _len = 0 Then
        Exit
    Endif
    If Max7219_scrollIdx >= _len Then
        Max7219_scrollIdx = 0
    Endif

    ' Optional: push current frame before shifting to keep prior behavior
    Call MAX7219_WriteFrameBuffer_Generic(AddressData)

    y = 0
    For i = 0 To 7
        crry = 0
        For j = 0 To MAX7219_MATRIX_COUNT - 1
            b = Pointer(AddressData + y + j)
            Pointer(AddressData + y + j) = (b << 1) Or crry
            crry = (b >> 7) And 1
        Next j
        y = y + MAX7219_MATRIX_COUNT
    Next i

    Max7219_scrollX = Max7219_scrollX + 1
    If Max7219_scrollX > 7 Then
        Call MAX7219_RotateMatrix90(AddressData, _string(Max7219_scrollIdx), MAX7219_MATRIX_COUNT)
        Max7219_scrollX = 0
        Max7219_scrollIdx = Max7219_scrollIdx + 1
        If Max7219_scrollIdx >= _len Then
            Max7219_scrollIdx = 0
        Endif
    Endif
End Function

' RightShift
' Shifts the full width RIGHT by one per row.
' Every 8 shifts, injects the next glyph (rotated) into the right-most matrix.
Function RightShift(AddressData As Word, _string As String) As Byte
    Dim i As Byte
    Dim j As Byte
    Dim y As Word
    Dim crry As Byte
    Dim b As Byte
    Dim _len As Byte

    _len = Len(_string)
    If _len = 0 Then
        Exit
    Endif
    If Max7219_scrollIdx >= _len Then
        Max7219_scrollIdx = _len - 1
    Endif

    ' Optional: push current frame before shifting to mirror left-shift behavior
    Call MAX7219_WriteFrameBuffer_Generic(AddressData)

    y = 0
    For i = 0 To 7
        crry = 0
        For j = MAX7219_MATRIX_COUNT - 1 To 0 Step -1
            b = Pointer(AddressData + y + j)
            Pointer(AddressData + y + j) = (b >> 1) Or (crry << 7)
            crry = b And 1
        Next j
        y = y + MAX7219_MATRIX_COUNT
    Next i

    Max7219_scrollX = Max7219_scrollX + 1
    If Max7219_scrollX > 7 Then
        Call MAX7219_RotateMatrix90(AddressData, _string(Max7219_scrollIdx), 1)
        Max7219_scrollX = 0
        If Max7219_scrollIdx > 0 Then
            Max7219_scrollIdx = Max7219_scrollIdx - 1
        Else
            Max7219_scrollIdx = _len - 1
        Endif
    Endif
End Function
'======================================================================================
' Bitmap font (8x8 monospace) via LookUp tables
'======================================================================================

'--------------------------------------------------------------------------------------
' Function: MAX7219_GlcdAscii
' Purpose : Returns the 8-bit mask for a given ASCII glyph row (index 0..7).
' Params  : _digito - ASCII code (or special code)
'           _indice - Row index (0..7). Each glyph is 8 rows tall.
' Returns : Byte mask for the requested glyph row.
'--------------------------------------------------------------------------------------
Function MAX7219_GlcdAscii(_digito As Byte, _indice As Byte) As Byte
    Symbol _mask = MAX7219_GlcdAscii

            Select Case _digito
                Case " "
                    _mask = LookUp(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), _indice  // SPACE
                Case "!"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00), _indice  // !
                Case 34
                    _mask = LookUp(0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00), _indice  // "
                Case "#"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x14, 0x3e, 0x14, 0x3e, 0x14), _indice  // #
                Case "$"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12), _indice  // $
                Case "%"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x43, 0x33, 0x08, 0x66, 0x61), _indice  // %
                Case "&"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x36, 0x49, 0x55, 0x22, 0x50), _indice  // &
                Case "'"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x00, 0x05, 0x03, 0x00, 0x00), _indice  // '
                Case "("
                    _mask = LookUp(0x00, 0x00, 0x00, 0x00, 0x1c, 0x22, 0x41, 0x00), _indice  // (
                Case ")"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x00, 0x41, 0x22, 0x1c, 0x00), _indice  // )
                Case "*"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x14, 0x08, 0x3e, 0x08, 0x14), _indice  // *
                Case "+"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x08, 0x08, 0x3e, 0x08, 0x08), _indice  // +
                Case ","
                    _mask = LookUp(0x00, 0x00, 0x00, 0x00, 0x50, 0x30, 0x00, 0x00), _indice  // ,
                Case "-"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x08), _indice  // -
                Case "."
                    _mask = LookUp(0x00, 0x00, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00), _indice  // .
                Case "/"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x20, 0x10, 0x08, 0x04, 0x02), _indice  // /
                Case "0"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x3e, 0x51, 0x49, 0x45, 0x3e), _indice  // 0
                Case "1"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x00, 0x04, 0x02, 0x7f, 0x00), _indice  // 1
                Case "2"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x42, 0x61, 0x51, 0x49, 0x46), _indice  // 2
                Case "3"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x22, 0x41, 0x49, 0x49, 0x36), _indice  // 3
                Case "4"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x18, 0x14, 0x12, 0x7f, 0x10), _indice  // 4
                Case "5"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x27, 0x45, 0x45, 0x45, 0x39), _indice  // 5
                Case "6"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x3e, 0x49, 0x49, 0x49, 0x32), _indice  // 6
                Case "7"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x01, 0x01, 0x71, 0x09, 0x07), _indice  // 7
                Case "8"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x36, 0x49, 0x49, 0x49, 0x36), _indice  // 8
                Case "9"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x26, 0x49, 0x49, 0x49, 0x3e), _indice  // 9
                Case ":"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x00, 0x36, 0x36, 0x00, 0x00), _indice  // :
                Case ";"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x00, 0x56, 0x36, 0x00, 0x00), _indice  // ;
                Case "<"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x08, 0x14, 0x22, 0x41, 0x00), _indice  // <
                Case "="
                    _mask = LookUp(0x00, 0x00, 0x00, 0x14, 0x14, 0x14, 0x14, 0x14), _indice  // =
                Case ">"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x00, 0x41, 0x22, 0x14, 0x08), _indice  // >
                Case "?"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x02, 0x01, 0x51, 0x09, 0x06), _indice  // ?
                Case "@"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x3e, 0x41, 0x59, 0x55, 0x5e), _indice  // @
                Case "A"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x7e, 0x09, 0x09, 0x09, 0x7e), _indice  // A
                Case "B"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x7f, 0x49, 0x49, 0x49, 0x36), _indice  // B
                Case "C"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x3e, 0x41, 0x41, 0x41, 0x22), _indice  // C
                Case "D"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x7f, 0x41, 0x41, 0x41, 0x3e), _indice  // D
                Case "E"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x7f, 0x49, 0x49, 0x49, 0x41), _indice  // E
                Case "F"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x7f, 0x09, 0x09, 0x09, 0x01), _indice  // F
                Case "G"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x3e, 0x41, 0x41, 0x49, 0x3a), _indice  // G
                Case "H"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x7f, 0x08, 0x08, 0x08, 0x7f), _indice  // H
                Case "I"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x00, 0x41, 0x7f, 0x41, 0x00), _indice  // I
                Case "J"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x30, 0x40, 0x40, 0x40, 0x3f), _indice  // J
                Case "K"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x7f, 0x08, 0x14, 0x22, 0x41), _indice  // K
                Case "L"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x7f, 0x40, 0x40, 0x40, 0x40), _indice  // L
                Case "M"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x7f, 0x02, 0x0c, 0x02, 0x7f), _indice  // M
                Case "N"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x7f, 0x02, 0x04, 0x08, 0x7f), _indice  // N
                Case "O"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x3e, 0x41, 0x41, 0x41, 0x3e), _indice  // O
                Case "P"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x7f, 0x09, 0x09, 0x09, 0x06), _indice  // P
                Case "Q"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x1e, 0x21, 0x21, 0x21, 0x5e), _indice  // Q
                Case "R"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x7f, 0x09, 0x09, 0x09, 0x76), _indice  // R
                Case "S"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x26, 0x49, 0x49, 0x49, 0x32), _indice  // S
                Case "T"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x01, 0x01, 0x7f, 0x01, 0x01), _indice  // T
                Case "U"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x3f, 0x40, 0x40, 0x40, 0x3f), _indice  // U
                Case "V"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x1f, 0x20, 0x40, 0x20, 0x1f), _indice  // V
                Case "W"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x7f, 0x20, 0x10, 0x20, 0x7f), _indice  // W
                Case "X"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x41, 0x22, 0x1c, 0x22, 0x41), _indice  // X
                Case "Y"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x07, 0x08, 0x70, 0x08, 0x07), _indice  // Y
                Case "Z"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x61, 0x51, 0x49, 0x45, 0x43), _indice  // Z
                Case "["
                    _mask = LookUp(0x00, 0x00, 0x00, 0x00, 0x7f, 0x41, 0x00, 0x00), _indice  // [
                Case "\"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x02, 0x04, 0x08, 0x10, 0x20), _indice  ' \
                Case "]"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x7f, 0x00), _indice  // ]
                Case "^"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x04, 0x02, 0x01, 0x02, 0x04), _indice  // ^
                Case "_"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x40), _indice  // _
                Case "`"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x04, 0x00), _indice  // `
                Case "a"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x20, 0x54, 0x54, 0x54, 0x78), _indice  // a
                Case "b"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x7f, 0x44, 0x44, 0x44, 0x38), _indice  // b
                Case "c"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x38, 0x44, 0x44, 0x44, 0x44), _indice  // c
                Case "d"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x38, 0x44, 0x44, 0x44, 0x7f), _indice  // d
                Case "e"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x38, 0x54, 0x54, 0x54, 0x18), _indice  // e
                Case "f"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x04, 0x04, 0x7e, 0x05, 0x05), _indice  // f
                Case "g"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x08, 0x54, 0x54, 0x54, 0x3c), _indice  // g
                Case "h"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x7f, 0x08, 0x04, 0x04, 0x78), _indice  // h
                Case "i"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x00, 0x44, 0x7d, 0x40, 0x00), _indice  ' i
                Case "j"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x20, 0x40, 0x44, 0x3d, 0x00), _indice  ' j
                Case "k"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x7f, 0x10, 0x28, 0x44, 0x00), _indice  // k
                Case "l"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x00, 0x41, 0x7f, 0x40, 0x00), _indice  // l
                Case "m"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x7c, 0x04, 0x78, 0x04, 0x78), _indice  // m
                Case "n"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x7c, 0x08, 0x04, 0x04, 0x78), _indice  // n
                Case "o"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x38, 0x44, 0x44, 0x44, 0x38), _indice  // o
                Case "p"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x7c, 0x14, 0x14, 0x14, 0x08), _indice  // p
                Case "q"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x08, 0x14, 0x14, 0x14, 0x7c), _indice  // q
                Case "r"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x00, 0x7c, 0x08, 0x04, 0x04), _indice  // r
                Case "s"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x48, 0x54, 0x54, 0x54, 0x20), _indice  // s
                Case "t"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x04, 0x04, 0x3f, 0x44, 0x44), _indice  // t
                Case "u"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x3c, 0x40, 0x40, 0x20, 0x7c), _indice  // u
                Case "v"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x1c, 0x20, 0x40, 0x20, 0x1c), _indice  // v
                Case "w"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x3c, 0x40, 0x30, 0x40, 0x3c), _indice  // w
                Case "x"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x44, 0x28, 0x10, 0x28, 0x44), _indice  // x
                Case "y"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x0c, 0x50, 0x50, 0x50, 0x3c), _indice  // y
                Case "z"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x44, 0x64, 0x54, 0x4c, 0x44), _indice  // z
                Case "{"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x00, 0x08, 0x36, 0x41, 0x41), _indice  // {
                Case "|"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00), _indice  // |
                Case "}"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x41, 0x41, 0x36, 0x08, 0x00), _indice  // }
                Case "~"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x02, 0x01, 0x02, 0x04, 0x02), _indice  // ~
                Case "Ñ"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x7e, 0x05, 0x09, 0x11, 0x7e), _indice  // Ñ
                Case "ñ"
                    _mask = LookUp(0x00, 0x00, 0x00, 0x78, 0x12, 0x0a, 0x0a, 0x70), _indice  ' ñ
                Case 1 ' Smiley
                    _mask = LookUp(0x00, %01111110, %10010001, %10100101, %10100001, %10100101, %10010001, %01111110), _indice ' :)
                Case 3 ' Heart
                    _mask = LookUp(0x00, 0x00, 0x00, %00011100, %00100010, %01000100, %00100010, %00011100), _indice            ' <3
            EndSelect
End Function