Maxim Integrated / Mbed OS MAXREFDES155#

Dependencies:   MaximInterface

Text.cpp

Committer:
IanBenzMaxim
Date:
2017-04-10
Revision:
10:71359af61af8
Parent:
8:a0d75dff3c9b
Child:
11:989eabe2a376

File content as of revision 10:71359af61af8:

/*******************************************************************************
* Copyright (C) 2017 Maxim Integrated Products, Inc., All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Except as contained in this notice, the name of Maxim Integrated
* Products, Inc. shall not be used except as stated in the Maxim Integrated
* Products, Inc. Branding Policy.
*
* The mere transfer of this software does not imply any licenses
* of trade secrets, proprietary technology, copyrights, patents,
* trademarks, maskwork rights, or any other form of intellectual
* property whatsoever. Maxim Integrated Products, Inc. retains all
* ownership rights.
*******************************************************************************/

#include <algorithm>
#include <functional>
#include <list>
#include "Bitmap.hpp"
#include "Display.hpp"
#include "Text.hpp"

using std::string;

static const int invalidWidthHeight = -1;

static const int characterWidth = 5;
static const int characterHeight = 7;

static const unsigned char printableCharBegin = 0x20;
static const unsigned char printableCharEnd = 0x7E;

static const uint8_t characterMap[][characterWidth] = {
    { 0x00, 0x00, 0x00, 0x00, 0x00 }, // ' '
    { 0x00, 0x00, 0xF2, 0x00, 0x00 }, // '!'
    { 0x00, 0xE0, 0x00, 0xE0, 0x00 }, // '"'
    { 0x28, 0xFE, 0x28, 0xFE, 0x28 }, // '#'
    { 0x24, 0x54, 0xFE, 0x54, 0x48 }, // '$'
    { 0xC4, 0xC8, 0x10, 0x26, 0x46 }, // '%'
    { 0x6C, 0x92, 0xAA, 0x44, 0x0A }, // '&'
    { 0x00, 0xA0, 0xC0, 0x00, 0x00 }, // '''
    { 0x00, 0x28, 0x44, 0x82, 0x00 }, // '('
    { 0x00, 0x82, 0x44, 0x38, 0x00 }, // ')'
    { 0x28, 0x10, 0x7C, 0x10, 0x28 }, // '*'
    { 0x10, 0x10, 0x7C, 0x10, 0x10 }, // '+'
    { 0x00, 0x0A, 0x0C, 0x00, 0x00 }, // ','
    { 0x10, 0x10, 0x10, 0x10, 0x10 }, // '-'
    { 0x00, 0x06, 0x06, 0x00, 0x00 }, // '.'
    { 0x04, 0x08, 0x10, 0x20, 0x40 }, // '/'
    { 0x7C, 0x8A, 0x92, 0xA2, 0x7C }, // '0'
    { 0x00, 0x42, 0xFE, 0x02, 0x00 }, // '1'
    { 0x42, 0x86, 0x8A, 0x92, 0x62 }, // '2'
    { 0x84, 0x82, 0xA2, 0xD2, 0x8C }, // '3'
    { 0x18, 0x28, 0x48, 0xFE, 0x08 }, // '4'
    { 0xE4, 0xA2, 0xA2, 0xA2, 0x9C }, // '5'
    { 0x3C, 0x52, 0x92, 0x92, 0x0C }, // '6'
    { 0x80, 0x8E, 0x90, 0xA0, 0xC0 }, // '7'
    { 0x6C, 0x92, 0x92, 0x92, 0x6C }, // '8'
    { 0x60, 0x92, 0x92, 0x94, 0x78 }, // '9'
    { 0x00, 0x6C, 0x6C, 0x00, 0x00 }, // ':'
    { 0x00, 0x6A, 0x6C, 0x00, 0x00 }, // ';'
    { 0x10, 0x28, 0x44, 0x82, 0x00 }, // '<'
    { 0x28, 0x28, 0x28, 0x28, 0x28 }, // '='
    { 0x00, 0x82, 0x44, 0x28, 0x10 }, // '>'
    { 0x40, 0x80, 0x8A, 0x90, 0x60 }, // '?'
    { 0x4C, 0x92, 0x9E, 0x82, 0x7C }, // '@'
    { 0x7E, 0x88, 0x88, 0x88, 0x7E }, // 'A'
    { 0xFE, 0x92, 0x92, 0x92, 0x6C }, // 'B'
    { 0x7C, 0x82, 0x82, 0x82, 0x44 }, // 'C'
    { 0xFE, 0x82, 0x82, 0x44, 0x38 }, // 'D'
    { 0xFE, 0x92, 0x92, 0x92, 0x82 }, // 'E'
    { 0xFE, 0x90, 0x90, 0x90, 0x80 }, // 'F'
    { 0x7C, 0x82, 0x92, 0x92, 0x5E }, // 'G'
    { 0xFE, 0x10, 0x10, 0x10, 0xFE }, // 'H'
    { 0x00, 0x82, 0xFE, 0x82, 0x00 }, // 'I'
    { 0x04, 0x02, 0x82, 0xFC, 0x80 }, // 'J'
    { 0xFE, 0x10, 0x28, 0x44, 0x82 }, // 'K'
    { 0xFE, 0x02, 0x02, 0x02, 0x02 }, // 'L'
    { 0xFE, 0x40, 0x21, 0x40, 0xFE }, // 'M'
    { 0xFE, 0x20, 0x10, 0x08, 0xFE }, // 'N'
    { 0x7C, 0x82, 0x82, 0x82, 0x7C }, // 'O'
    { 0xFE, 0x90, 0x90, 0x90, 0x60 }, // 'P'
    { 0x7C, 0x82, 0x8A, 0x82, 0x7A }, // 'Q'
    { 0xFE, 0x90, 0x98, 0x94, 0x62 }, // 'R'
    { 0x62, 0x92, 0x92, 0x92, 0x8C }, // 'S'
    { 0x80, 0x80, 0xFE, 0x80, 0x80 }, // 'T'
    { 0xFC, 0x02, 0x02, 0x02, 0xFC }, // 'U'
    { 0xF8, 0x04, 0x02, 0x04, 0xF8 }, // 'V'
    { 0xFC, 0x02, 0x1C, 0x02, 0xFC }, // 'W'
    { 0xC6, 0x28, 0x10, 0x28, 0xC6 }, // 'X'
    { 0xF0, 0x10, 0x0F, 0x10, 0xF0 }, // 'Y'
    { 0x86, 0x8A, 0x92, 0xA2, 0xC2 }, // 'Z'
    { 0x00, 0xFE, 0x82, 0x82, 0x00 }, // '['
    { 0x40, 0x20, 0x10, 0x08, 0x04 }, // '\'
    { 0x00, 0x82, 0x82, 0xFE, 0x00 }, // ']'
    { 0x20, 0x40, 0x80, 0x40, 0x20 }, // '^'
    { 0x02, 0x02, 0x02, 0x02, 0x02 }, // '_'
    { 0x00, 0x80, 0x40, 0x20, 0x00 }, // '`'
    { 0x04, 0x2A, 0x2A, 0x2A, 0x1E }, // 'a'
    { 0xFE, 0x12, 0x22, 0x22, 0x1C }, // 'b'
    { 0x1C, 0x22, 0x22, 0x22, 0x04 }, // 'c'
    { 0x1C, 0x22, 0x22, 0x12, 0xFE }, // 'd'
    { 0x1C, 0x2A, 0x2A, 0x2A, 0x18 }, // 'e'
    { 0x10, 0x7E, 0x90, 0x80, 0x40 }, // 'f'
    { 0x30, 0x4A, 0x4A, 0x4A, 0x7C }, // 'g'
    { 0xFE, 0x10, 0x20, 0x20, 0x1E }, // 'h'
    { 0x00, 0x22, 0xBE, 0x02, 0x00 }, // 'i'
    { 0x04, 0x02, 0x22, 0xBC, 0x00 }, // 'j'
    { 0xFE, 0x08, 0x14, 0x22, 0x00 }, // 'k'
    { 0x00, 0x82, 0xFE, 0x02, 0x00 }, // 'l'
    { 0x3E, 0x20, 0x18, 0x20, 0x1E }, // 'm'
    { 0x3E, 0x10, 0x20, 0x20, 0x1E }, // 'n'
    { 0x1C, 0x22, 0x22, 0x22, 0x1C }, // 'o'
    { 0x3E, 0x28, 0x28, 0x28, 0x10 }, // 'p'
    { 0x10, 0x28, 0x28, 0x18, 0x3E }, // 'q'
    { 0x3E, 0x10, 0x20, 0x20, 0x10 }, // 'r'
    { 0x12, 0x2A, 0x2A, 0x2A, 0x04 }, // 's'
    { 0x20, 0xFC, 0x22, 0x02, 0x04 }, // 't'
    { 0x3C, 0x02, 0x02, 0x04, 0x3E }, // 'u'
    { 0x38, 0x04, 0x02, 0x04, 0x38 }, // 'v'
    { 0x3C, 0x02, 0x0C, 0x02, 0x3C }, // 'w'
    { 0x22, 0x14, 0x08, 0x14, 0x22 }, // 'x'
    { 0x30, 0x0A, 0x0A, 0x0A, 0x3C }, // 'y'
    { 0x22, 0x26, 0x2A, 0x32, 0x22 }, // 'z'
    { 0x00, 0x10, 0x6C, 0x82, 0x00 }, // '{'
    { 0x00, 0x00, 0xFE, 0x00, 0x00 }, // '|'
    { 0x00, 0x82, 0x6C, 0x10, 0x00 }, // '}'
    { 0x10, 0x20, 0x10, 0x08, 0x10 }  // '~'
};

static bool charPrintable(unsigned char c)
{
    return (c >= printableCharBegin) && (c <= printableCharEnd);
}

static void removeNonprintableChars(string & s)
{
    s.erase(std::remove_if(s.begin(), s.end(), std::not1(std::ptr_fun(charPrintable))), s.end());
}

static bool compareStringLength(const string & a, const string & b)
{
    return a.length() < b.length();
}

// Functor trims string if it exceeds a certain length.
class TrimString
{
public:
    TrimString(string::size_type maxSize) : maxSize(maxSize) { }
    
    void operator()(string & s)
    {
        if (s.size() > maxSize)
        {
            s.resize(maxSize);
        }
    }
    
private:
    string::size_type maxSize;
};

// Split a string based on a character token. Token will be removed.
static std::list<string> tokenizeString(const string & toSplit, char token)
{    
    std::list<string> toSplitLines;
    string::size_type beginIdx = 0, endIdx;
    do
    {
        endIdx = toSplit.find(token, beginIdx);
        toSplitLines.push_back(toSplit.substr(beginIdx, endIdx == string::npos ? string::npos : endIdx - beginIdx));
        beginIdx = endIdx + 1;
    } while (endIdx != string::npos);
    return toSplitLines;
}

// Word wrap string into lines based on a specified line length.
static std::list<string> wrapLines(const string & toSplit, const string::size_type lineLen)
{    
    std::list<string> toSplitLines;
    string::size_type beginIdx = 0;
    if (lineLen > 0)
    {
        // Split lines as much as necessary.
        string::size_type endIdx = lineLen;
        while (((toSplit.length() - beginIdx) > lineLen) && (endIdx != string::npos))
        {           
            endIdx = toSplit.rfind(' ', endIdx);
            if ((endIdx == string::npos) || (endIdx <= beginIdx))
            {
                // Current word is too long to split. Find end of current word.
                endIdx = toSplit.find(' ', beginIdx);
            }
            if (endIdx != string::npos)
            {
                toSplitLines.push_back(toSplit.substr(beginIdx, endIdx - beginIdx));
                beginIdx = endIdx + 1;
                endIdx = beginIdx + lineLen;
            }
        }
    }
    // Last line is any remaining characters.
    toSplitLines.push_back(toSplit.substr(beginIdx, toSplit.length() - beginIdx));
    return toSplitLines;
}

Text::Text() : m_text(), m_wordWrap(false), m_charSpacing(1), m_lineSpacing(1),
    m_textLines(), m_preferredWidth(invalidWidthHeight), m_preferredHeight(invalidWidthHeight) { }

void Text::setText(const string & text)
{
    if (m_text != text)
    {
        m_text = text;
        invalidate();
        m_preferredWidth = m_preferredHeight = invalidWidthHeight;
    }
}

void Text::setWordWrap(bool wordWrap)
{
    if (m_wordWrap != wordWrap)
    {
        m_wordWrap = wordWrap;
        invalidate();
        m_preferredWidth = m_preferredHeight = invalidWidthHeight;
    }
}

void Text::setLineSpacing(int lineSpacing)
{
    if (lineSpacing > 0 && m_lineSpacing != lineSpacing)
    {
        m_lineSpacing = lineSpacing;
        invalidate();
        m_preferredHeight = invalidWidthHeight;
    }
}

void Text::setCharSpacing(int charSpacing)
{
    if (charSpacing > 0 && m_charSpacing != charSpacing)
    {
        m_charSpacing = charSpacing;
        invalidate();
        m_preferredWidth = invalidWidthHeight;
        if (wordWrap())
        {
            m_preferredHeight = invalidWidthHeight;
        }
    }
}

int Text::preferredWidth() const
{
    if (m_preferredWidth == invalidWidthHeight)
    {
        calculateLayout();
    }
    return m_preferredWidth;
}

int Text::preferredHeight() const
{
    if (m_preferredHeight == invalidWidthHeight)
    {
        calculateLayout();
    }
    return m_preferredHeight;
}

void Text::resized()
{
    m_preferredWidth = invalidWidthHeight;
    if (wordWrap())
    {
        m_preferredHeight = invalidWidthHeight;
    }
}

void Text::doRender(Bitmap & bitmap, int xOffset, int yOffset) const
{   
    using std::list;
    
    // Ensure layout is up to date.
    if (m_preferredWidth == invalidWidthHeight || m_preferredHeight == invalidWidthHeight)
    {
        calculateLayout();
    }
    
    // Render each line.
    int lineNum = 0;
    for (list<string>::const_iterator lineIt = m_textLines.begin(); lineIt != m_textLines.end(); lineIt++)
    {
        // Render each character.
        for (string::size_type charNum = 0; charNum < lineIt->length(); charNum++)
        {
            Bitmap character(characterWidth, characterHeight,
                &characterMap[(*lineIt)[charNum] - printableCharBegin][0]);
            bitmap.overlay(
                character,
                xOffset + x() + charNum * (characterWidth + m_charSpacing),
                yOffset + y() + lineNum * (characterHeight + m_lineSpacing)
            );
        }
        lineNum++;
    }
}

void Text::calculateLayout() const
{
    using std::list;
                
    // Split string into lines.
    m_textLines = tokenizeString(m_text, '\n');
    
    // Remove non-printable characters.
    std::for_each(m_textLines.begin(), m_textLines.end(), removeNonprintableChars);
        
    const int lineLen =
        (width() / (characterWidth + m_charSpacing)) +
        (((width() % (characterWidth + m_charSpacing)) >= characterWidth) ? 1 : 0);
    const int numLines =
        (height() / (characterHeight + m_lineSpacing)) +
        (((height() % (characterHeight + m_lineSpacing)) >= characterHeight) ? 1 : 0);
        
    // Word wrap lines if enabled.
    if (m_wordWrap)
    {
        list<string>::iterator lineIt = m_textLines.begin();
        while (lineIt != m_textLines.end())
        {
            list<string>::iterator nextLineIt = lineIt;
            nextLineIt++;
            
            // Wrap current line.
            list<string> wrappedLines = wrapLines(*lineIt, lineLen);
            m_textLines.splice(lineIt, wrappedLines);
            // Remove old line.
            m_textLines.erase(lineIt);

            lineIt = nextLineIt;
        }
    }
    
    // Calculate preferred size.
    string::size_type maxLineLength =
        std::max_element(m_textLines.begin(), m_textLines.end(), compareStringLength)->length();
    m_preferredWidth = (maxLineLength > 0) ?
        (maxLineLength * characterWidth) + ((maxLineLength - 1) * m_charSpacing) : 1;
    m_preferredHeight =
        (m_textLines.size() * characterHeight) + ((m_textLines.size() - 1) * m_lineSpacing);
    
    // Remove clipped text.
    if (m_textLines.size() > static_cast<list<string>::size_type>(numLines))
        m_textLines.resize(numLines);
    std::for_each(m_textLines.begin(), m_textLines.end(), TrimString(lineLen));
}