Maxim Integrated / Mbed OS MAXREFDES155#

Dependencies:   MaximInterface

src/Text.cpp

Committer:
IanBenzMaxim
Date:
2019-10-03
Revision:
16:a004191a79ab
Parent:
Text.cpp@ 13:6a6225690c2e

File content as of revision 16:a004191a79ab:

/*******************************************************************************
* Copyright (C) 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[][characterHeight] = {
    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // ' '
    {0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x20}, // '!'
    {0x50, 0x50, 0x50, 0x00, 0x00, 0x00, 0x00}, // '"'
    {0x50, 0x50, 0xF8, 0x50, 0xF8, 0x50, 0x50}, // '#'
    {0x20, 0x78, 0xA0, 0x70, 0x28, 0xF0, 0x20}, // '$'
    {0xC0, 0xC8, 0x10, 0x20, 0x40, 0x98, 0x18}, // '%'
    {0x60, 0x90, 0xA0, 0x40, 0xA8, 0x90, 0x68}, // '&'
    {0x60, 0x20, 0x40, 0x00, 0x00, 0x00, 0x00}, // '''
    {0x10, 0x20, 0x40, 0x40, 0x40, 0x20, 0x10}, // '('
    {0x40, 0x20, 0x10, 0x10, 0x10, 0x20, 0x40}, // ')'
    {0x00, 0x20, 0xA8, 0x70, 0xA8, 0x20, 0x00}, // '*'
    {0x00, 0x20, 0x20, 0xF8, 0x20, 0x20, 0x00}, // '+'
    {0x00, 0x00, 0x00, 0x00, 0x60, 0x20, 0x40}, // ','
    {0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00}, // '-'
    {0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60}, // '.'
    {0x00, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00}, // '/'
    {0x70, 0x88, 0x98, 0xA8, 0xC8, 0x88, 0x70}, // '0'
    {0x20, 0x60, 0x20, 0x20, 0x20, 0x20, 0x70}, // '1'
    {0x70, 0x88, 0x08, 0x10, 0x20, 0x40, 0xF8}, // '2'
    {0xF8, 0x10, 0x20, 0x10, 0x08, 0x88, 0x70}, // '3'
    {0x10, 0x30, 0x50, 0x90, 0xF8, 0x10, 0x10}, // '4'
    {0xF8, 0x80, 0xF0, 0x08, 0x08, 0x88, 0x70}, // '5'
    {0x30, 0x40, 0x80, 0xF0, 0x88, 0x88, 0x70}, // '6'
    {0xF8, 0x08, 0x10, 0x20, 0x40, 0x40, 0x40}, // '7'
    {0x70, 0x88, 0x88, 0x70, 0x88, 0x88, 0x70}, // '8'
    {0x70, 0x88, 0x88, 0x78, 0x08, 0x10, 0x60}, // '9'
    {0x00, 0x60, 0x60, 0x00, 0x60, 0x60, 0x00}, // ':'
    {0x00, 0x60, 0x60, 0x00, 0x60, 0x20, 0x40}, // ';'
    {0x10, 0x20, 0x40, 0x80, 0x40, 0x20, 0x10}, // '<'
    {0x00, 0x00, 0xF8, 0x00, 0xF8, 0x00, 0x00}, // '='
    {0x40, 0x20, 0x10, 0x08, 0x10, 0x20, 0x40}, // '>'
    {0x70, 0x88, 0x08, 0x10, 0x20, 0x00, 0x20}, // '?'
    {0x70, 0x88, 0x08, 0x68, 0xA8, 0xA8, 0x70}, // '@'
    {0x70, 0x88, 0x88, 0x88, 0xF8, 0x88, 0x88}, // 'A'
    {0xF0, 0x88, 0x88, 0xF0, 0x88, 0x88, 0xF0}, // 'B'
    {0x70, 0x88, 0x80, 0x80, 0x80, 0x88, 0x70}, // 'C'
    {0xE0, 0x90, 0x88, 0x88, 0x88, 0x90, 0xE0}, // 'D'
    {0xF8, 0x80, 0x80, 0xF0, 0x80, 0x80, 0xF8}, // 'E'
    {0xF8, 0x80, 0x80, 0xF0, 0x80, 0x80, 0x80}, // 'F'
    {0x70, 0x88, 0x80, 0xB8, 0x88, 0x88, 0x78}, // 'G'
    {0x88, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x88}, // 'H'
    {0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70}, // 'I'
    {0x38, 0x10, 0x10, 0x10, 0x10, 0x90, 0x60}, // 'J'
    {0x88, 0x90, 0xA0, 0xC0, 0xA0, 0x90, 0x88}, // 'K'
    {0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xF8}, // 'L'
    {0x88, 0xD8, 0xA8, 0xA8, 0x88, 0x88, 0x88}, // 'M'
    {0x88, 0x88, 0xC8, 0xA8, 0x98, 0x88, 0x88}, // 'N'
    {0x70, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70}, // 'O'
    {0xF0, 0x88, 0x88, 0xF0, 0x80, 0x80, 0x80}, // 'P'
    {0x70, 0x88, 0x88, 0x88, 0xA8, 0x90, 0x68}, // 'Q'
    {0xF0, 0x88, 0x88, 0xF0, 0xA0, 0x90, 0x88}, // 'R'
    {0x78, 0x80, 0x80, 0x70, 0x08, 0x08, 0xF0}, // 'S'
    {0xF8, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20}, // 'T'
    {0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70}, // 'U'
    {0x88, 0x88, 0x88, 0x88, 0x88, 0x50, 0x20}, // 'V'
    {0x88, 0x88, 0x88, 0xA8, 0xA8, 0xA8, 0x50}, // 'W'
    {0x88, 0x88, 0x50, 0x20, 0x50, 0x88, 0x88}, // 'X'
    {0x88, 0x88, 0x88, 0x50, 0x20, 0x20, 0x20}, // 'Y'
    {0xF8, 0x08, 0x10, 0x20, 0x40, 0x80, 0xF8}, // 'Z'
    {0x70, 0x40, 0x40, 0x40, 0x40, 0x40, 0x70}, // '['
    {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x00}, // '\'
    {0x70, 0x10, 0x10, 0x10, 0x10, 0x10, 0x70}, // ']'
    {0x20, 0x50, 0x88, 0x00, 0x00, 0x00, 0x00}, // '^'
    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8}, // '_'
    {0x40, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00}, // '`'
    {0x00, 0x00, 0x70, 0x08, 0x78, 0x88, 0x78}, // 'a'
    {0x80, 0x80, 0xB0, 0xC8, 0x88, 0x88, 0xF0}, // 'b'
    {0x00, 0x00, 0x70, 0x80, 0x80, 0x88, 0x70}, // 'c'
    {0x08, 0x08, 0x68, 0x98, 0x88, 0x88, 0x78}, // 'd'
    {0x00, 0x00, 0x70, 0x88, 0xF8, 0x80, 0x70}, // 'e'
    {0x30, 0x48, 0x40, 0xE0, 0x40, 0x40, 0x40}, // 'f'
    {0x00, 0x78, 0x88, 0x88, 0x78, 0x08, 0x70}, // 'g'
    {0x80, 0x80, 0xB0, 0xC8, 0x88, 0x88, 0x88}, // 'h'
    {0x20, 0x00, 0x60, 0x20, 0x20, 0x20, 0x70}, // 'i'
    {0x10, 0x00, 0x30, 0x10, 0x10, 0x90, 0x60}, // 'j'
    {0x80, 0x80, 0x90, 0xA0, 0xC0, 0xA0, 0x90}, // 'k'
    {0x60, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70}, // 'l'
    {0x00, 0x00, 0xD0, 0xA8, 0xA8, 0x88, 0x88}, // 'm'
    {0x00, 0x00, 0xB0, 0xC8, 0x88, 0x88, 0x88}, // 'n'
    {0x00, 0x00, 0x70, 0x88, 0x88, 0x88, 0x70}, // 'o'
    {0x00, 0x00, 0xF0, 0x88, 0xF0, 0x80, 0x80}, // 'p'
    {0x00, 0x00, 0x68, 0x98, 0x78, 0x08, 0x08}, // 'q'
    {0x00, 0x00, 0xB0, 0xC8, 0x80, 0x80, 0x80}, // 'r'
    {0x00, 0x00, 0x70, 0x80, 0x70, 0x08, 0xF0}, // 's'
    {0x40, 0x40, 0xE0, 0x40, 0x40, 0x48, 0x30}, // 't'
    {0x00, 0x00, 0x88, 0x88, 0x88, 0x98, 0x68}, // 'u'
    {0x00, 0x00, 0x88, 0x88, 0x88, 0x41, 0x20}, // 'v'
    {0x00, 0x00, 0x88, 0x88, 0xA8, 0xA8, 0x50}, // 'w'
    {0x00, 0x00, 0x88, 0x50, 0x20, 0x50, 0x88}, // 'x'
    {0x00, 0x00, 0x88, 0x88, 0x78, 0x08, 0x70}, // 'y'
    {0x00, 0x00, 0xF8, 0x10, 0x20, 0x40, 0xF8}, // 'z'
    {0x10, 0x20, 0x20, 0x40, 0x20, 0x20, 0x10}, // '{'
    {0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20}, // '|'
    {0x40, 0x20, 0x20, 0x10, 0x20, 0x20, 0x40}, // '}'
    {0x00, 0x00, 0x40, 0xA8, 0x10, 0x00, 0x00}  // '~'
};

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();
}

namespace {

// 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;
};

} // namespace

// 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()
    : text_(), wordWrap_(false), charSpacing_(1), lineSpacing_(1), textLines_(),
      preferredWidth_(invalidWidthHeight),
      preferredHeight_(invalidWidthHeight) {}

void Text::setText(const string & text) {
  if (text_ != text) {
    text_ = text;
    invalidate();
    preferredWidth_ = preferredHeight_ = invalidWidthHeight;
  }
}

void Text::setWordWrap(bool wordWrap) {
  if (wordWrap_ != wordWrap) {
    wordWrap_ = wordWrap;
    invalidate();
    preferredWidth_ = preferredHeight_ = invalidWidthHeight;
  }
}

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

void Text::setCharSpacing(int charSpacing) {
  if (charSpacing > 0 && charSpacing_ != charSpacing) {
    charSpacing_ = charSpacing;
    invalidate();
    preferredWidth_ = invalidWidthHeight;
    if (wordWrap()) {
      preferredHeight_ = invalidWidthHeight;
    }
  }
}

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

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

void Text::resized() {
  preferredWidth_ = invalidWidthHeight;
  if (wordWrap()) {
    preferredHeight_ = invalidWidthHeight;
  }
}

void Text::doRender(Bitmap & bitmap, int xOffset, int yOffset) const {
  using std::list;

  // Ensure layout is up to date.
  if (preferredWidth_ == invalidWidthHeight ||
      preferredHeight_ == invalidWidthHeight) {
    calculateLayout();
  }

  // Render each line.
  int lineNum = 0;
  for (list<string>::const_iterator lineIt = textLines_.begin();
       lineIt != textLines_.end(); ++lineIt) {
    // Render each character.
    for (string::size_type charNum = 0; charNum < lineIt->length(); ++charNum) {
      bitmap.overlay(xOffset + x() + charNum * (characterWidth + charSpacing_),
                     yOffset + y() + lineNum * (characterHeight + lineSpacing_),
                     &characterMap[(*lineIt)[charNum] - printableCharBegin][0],
                     characterHeight, characterWidth);
    }
    ++lineNum;
  }
}

void Text::calculateLayout() const {
  using std::list;

  // Split string into lines.
  textLines_ = tokenizeString(text_, '\n');

  // Remove non-printable characters.
  std::for_each(textLines_.begin(), textLines_.end(), removeNonprintableChars);

  const int lineLen =
      (width() / (characterWidth + charSpacing_)) +
      (((width() % (characterWidth + charSpacing_)) >= characterWidth) ? 1 : 0);
  const int numLines =
      (height() / (characterHeight + lineSpacing_)) +
      (((height() % (characterHeight + lineSpacing_)) >= characterHeight) ? 1
                                                                          : 0);

  // Word wrap lines if enabled.
  if (wordWrap_) {
    list<string>::iterator lineIt = textLines_.begin();
    while (lineIt != textLines_.end()) {
      list<string>::iterator nextLineIt = lineIt;
      ++nextLineIt;

      // Wrap current line.
      list<string> wrappedLines = wrapLines(*lineIt, lineLen);
      textLines_.splice(lineIt, wrappedLines);
      // Remove old line.
      textLines_.erase(lineIt);

      lineIt = nextLineIt;
    }
  }

  // Calculate preferred size.
  string::size_type maxLineLength =
      std::max_element(textLines_.begin(), textLines_.end(),
                       compareStringLength)
          ->length();
  preferredWidth_ = (maxLineLength > 0)
                        ? (maxLineLength * characterWidth) +
                              ((maxLineLength - 1) * charSpacing_)
                        : 1;
  preferredHeight_ = (textLines_.size() * characterHeight) +
                     ((textLines_.size() - 1) * lineSpacing_);

  // Remove clipped text.
  if (textLines_.size() > static_cast<list<string>::size_type>(numLines)) {
    textLines_.resize(numLines);
  }
  std::for_each(textLines_.begin(), textLines_.end(), TrimString(lineLen));
}