| /**************************************************************************** |
| * apps/graphics/nxwidgets/src/ctext.cxx |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| **************************************************************************** |
| * |
| * Portions of this package derive from Woopsi (http://woopsi.org/) and |
| * portions are original efforts. It is difficult to determine at this |
| * point what parts are original efforts and which parts derive from Woopsi. |
| * However, in any event, the work of Antony Dzeryn will be acknowledged |
| * in most NxWidget files. Thanks Antony! |
| * |
| * Copyright (c) 2007-2011, Antony Dzeryn |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * * Neither the names "Woopsi", "Simian Zombie" nor the |
| * names of its contributors may be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY Antony Dzeryn ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL Antony Dzeryn BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <stdint.h> |
| #include <stdbool.h> |
| |
| #include "graphics/nxwidgets/ctext.hxx" |
| #include "graphics/nxwidgets/cstringiterator.hxx" |
| |
| /**************************************************************************** |
| * Pre-Processor Definitions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Method Implementations |
| ****************************************************************************/ |
| |
| using namespace NXWidgets; |
| |
| /** |
| * Constructor. |
| * |
| * @param font The font to use for this text object. |
| * @param text A string that this text object should wrap around. |
| * @param width The pixel width at which the text should wrap. |
| */ |
| |
| CText::CText(CNxFont *font, const CNxString &text, nxgl_coord_t width) |
| : CNxString(text) |
| { |
| m_font = font; |
| m_width = width; |
| m_lineSpacing = 1; |
| wrap(); |
| } |
| |
| /** |
| * Set the text in the string. |
| * |
| * @param text Char array to use as the new data for this string. |
| */ |
| |
| void CText::setText(const CNxString &text) |
| { |
| CNxString::setText(text); |
| wrap(); |
| } |
| |
| /** |
| * Set the text in the string. |
| * |
| * @param text Char array to use as the new data for this string. |
| */ |
| |
| void CText::setText(FAR const char *text) |
| { |
| CNxString::setText(text); |
| wrap(); |
| } |
| |
| /** |
| * Set the text in the string. |
| * |
| * @param text Character to use as the new data for this string. |
| */ |
| |
| void CText::setText(const nxwidget_char_t text) |
| { |
| CNxString::setText(text); |
| wrap(); |
| } |
| |
| /** |
| * Append text to the end of the string. |
| * |
| * @param text String to append. |
| */ |
| |
| void CText::append(const CNxString &text) |
| { |
| CNxString::append(text); |
| wrap(getLength() - 1); |
| } |
| |
| /** |
| * Insert text at the specified character index. |
| * |
| * @param text The text to insert. |
| * @param index The char index to insert at. |
| */ |
| |
| void CText::insert(const CNxString &text, const int index) |
| { |
| CNxString::insert(text, index); |
| wrap(index); |
| } |
| |
| /** |
| * Remove all characters from the string from the start index onwards. |
| * |
| * @param startIndex The char index to start removing from. |
| */ |
| |
| void CText::remove(const int startIndex) |
| { |
| CNxString::remove(startIndex); |
| wrap(startIndex); |
| } |
| |
| /** |
| * Remove all characters from the string from the start index onwards. |
| * |
| * @param startIndex The char index to start removing from. |
| * @param count The number of chars to remove. |
| */ |
| |
| void CText::remove(const int startIndex, const int count) |
| { |
| CNxString::remove(startIndex, count); |
| wrap(startIndex); |
| } |
| |
| |
| /** |
| * Set the vertical spacing between rows of text. |
| * |
| * @param lineSpacing The line spacing. |
| */ |
| |
| void CText::setLineSpacing(nxgl_coord_t lineSpacing) |
| { |
| m_lineSpacing = lineSpacing; |
| wrap(); |
| } |
| |
| /** |
| * Sets the pixel width of the text; text wider than |
| * this will automatically wrap. |
| * |
| * @param width Maximum pixel width of the text. |
| */ |
| |
| void CText::setWidth(nxgl_coord_t width) |
| { |
| m_width = width; |
| wrap(); |
| } |
| |
| /** |
| * Set the font to use. |
| * |
| * @param font Pointer to the new font. |
| */ |
| |
| void CText::setFont(CNxFont* font) |
| { |
| m_font = font; |
| wrap(); |
| } |
| |
| /** |
| * Get the number of characters in the specified line number. |
| * |
| * @param lineNumber The line number to check. |
| * @return The number of characters in the line. |
| */ |
| |
| const int CText::getLineLength(const int lineNumber) const |
| { |
| if (lineNumber < getLineCount() - 1) |
| { |
| return m_linePositions[lineNumber + 1] - m_linePositions[lineNumber]; |
| } |
| |
| return getLength() - m_linePositions[lineNumber]; |
| } |
| |
| /** |
| * Get the number of characters in the specified line number, |
| * ignoring any trailing blank characters. |
| * |
| * @param lineNumber The line number to check. |
| * @return The number of characters in the line. |
| */ |
| |
| const int CText::getLineTrimmedLength(const int lineNumber) const |
| { |
| nxgl_coord_t length = getLineLength(lineNumber); |
| |
| // Loop through string until the end |
| |
| CStringIterator *iterator = newStringIterator(); |
| |
| // Get char at the end of the line |
| |
| if (iterator->moveTo(m_linePositions[lineNumber] + length - 1)) |
| { |
| do |
| { |
| if (!m_font->isCharBlank(iterator->getChar())) |
| { |
| break; |
| } |
| length--; |
| } |
| while (iterator->moveToPrevious() && (length > 0)); |
| |
| delete iterator; |
| return length; |
| } |
| |
| // May occur if data has been horribly corrupted somewhere |
| |
| delete iterator; |
| return 0; |
| } |
| |
| /** |
| * Get the width in pixels of the specified line number. |
| * |
| * @param lineNumber The line number to check. |
| * @return The pixel width of the line. |
| */ |
| |
| const nxgl_coord_t CText::getLinePixelLength(const int lineNumber) const |
| { |
| return m_font->getStringWidth(*this, getLineStartIndex(lineNumber), |
| getLineLength(lineNumber)); |
| } |
| |
| /** |
| * Get the width in pixels of the specified line number, |
| * ignoring any trailing blank characters. |
| * |
| * @param lineNumber The line number to check. |
| * @return The pixel width of the line. |
| */ |
| |
| const nxgl_coord_t CText::getLineTrimmedPixelLength(const int lineNumber) const |
| { |
| return m_font->getStringWidth(*this, getLineStartIndex(lineNumber), |
| getLineTrimmedLength(lineNumber)); |
| } |
| |
| /** |
| * Get a pointer to the CText object's font. |
| * |
| * @return Pointer to the font. |
| */ |
| |
| CNxFont *CText::getFont(void) const |
| { |
| return m_font; |
| } |
| |
| /** |
| * Removes lines of text from the start of the text buffer. |
| * |
| * @param lines Number of lines to remove |
| */ |
| |
| void CText::stripTopLines(const int lines) |
| { |
| // Get the start point of the text we want to keep |
| |
| nxgl_coord_t textStart = 0; |
| |
| for (int i = 0; i < lines; i++) |
| { |
| textStart += getLineLength(i); |
| } |
| |
| // Remove the characters from the start of the string to the found location |
| |
| remove(0, textStart); |
| |
| // Rewrap the text |
| |
| wrap(); |
| } |
| |
| /** |
| * Wrap all of the text. |
| */ |
| |
| void CText::wrap(void) |
| { |
| wrap(0); |
| } |
| |
| /** |
| * Wrap the text from the line containing the specified char index onwards. |
| * |
| * @param charIndex The index of the char to start wrapping from; note |
| * that the wrapping function will re-wrap that entire line of text. |
| */ |
| |
| void CText::wrap(int charIndex) |
| { |
| // Declare vars in advance of loop |
| |
| int pos = 0; |
| int lineWidth; |
| int breakIndex; |
| bool endReached = false; |
| |
| if (m_linePositions.size() == 0) |
| { |
| charIndex = 0; |
| } |
| |
| // If we're wrapping from an offset in the text, ensure that any existing data |
| // after the offset gets removed |
| |
| if (charIndex > 0) |
| { |
| // Remove wrapping data past this point |
| |
| // Get the index of the line in which the char index appears |
| |
| int lineIndex = getLineContainingCharIndex(charIndex); |
| |
| // Remove any longest line records that occur from the line index onwards |
| |
| while ((m_longestLines.size() > 0) && |
| (m_longestLines[m_longestLines.size() - 1].index >= lineIndex)) |
| { |
| m_longestLines.pop_back(); |
| } |
| |
| // If there are any longest line records remaining, update the text pixel width |
| // The last longest line record will always be the last valid longest line as |
| // the vector is sorted by length |
| |
| if (m_longestLines.size() > 0) |
| { |
| m_textPixelWidth = m_longestLines[m_longestLines.size() - 1].width; |
| } |
| else |
| { |
| m_textPixelWidth = 0; |
| } |
| |
| // Remove any wrapping data from after this line index onwards |
| |
| while ((m_linePositions.size() > 0) && |
| (m_linePositions.size() - 1 > (int)lineIndex)) |
| { |
| m_linePositions.pop_back(); |
| } |
| |
| // Adjust start position of wrapping loop so that it starts with |
| // the current line index |
| |
| if (m_linePositions.size() > 0) |
| { |
| pos = m_linePositions[m_linePositions.size() - 1]; |
| } |
| } |
| else |
| { |
| // Remove all wrapping data |
| |
| // Wipe the width variable |
| |
| m_textPixelWidth = 0; |
| |
| // Empty existing longest lines |
| |
| m_longestLines.clear(); |
| |
| // Empty existing line positions |
| |
| m_linePositions.clear(); |
| |
| // Push first line start into vector |
| |
| m_linePositions.push_back(0); |
| } |
| |
| // Loop through string until the end |
| |
| CStringIterator *iterator = newStringIterator(); |
| |
| while (!endReached) |
| { |
| breakIndex = 0; |
| lineWidth = 0; |
| |
| if (iterator->moveTo(pos)) |
| { |
| // Search for line breaks and valid breakpoints until we |
| // exceed the width of the text field or we run out of |
| // string to process |
| |
| while (lineWidth + m_font->getCharWidth(iterator->getChar()) <= m_width) |
| { |
| lineWidth += m_font->getCharWidth(iterator->getChar()); |
| |
| // Check for line return |
| |
| if (iterator->getChar() == '\n') |
| { |
| // Remember this breakpoint |
| |
| breakIndex = iterator->getIndex(); |
| break; |
| } |
| else if ((iterator->getChar() == ' ') || |
| (iterator->getChar() == ',') || |
| (iterator->getChar() == '.') || |
| (iterator->getChar() == '-') || |
| (iterator->getChar() == ':') || |
| (iterator->getChar() == ';') || |
| (iterator->getChar() == '?') || |
| (iterator->getChar() == '!') || |
| (iterator->getChar() == '+') || |
| (iterator->getChar() == '=') || |
| (iterator->getChar() == '/') || |
| (iterator->getChar() == '\0')) |
| { |
| // Remember the most recent breakpoint |
| |
| breakIndex = iterator->getIndex(); |
| } |
| |
| // Move to the next character |
| |
| if (!iterator->moveToNext()) |
| { |
| // No more text; abort loop |
| |
| endReached = true; |
| break; |
| } |
| } |
| } |
| else |
| { |
| endReached = true; |
| } |
| |
| if ((!endReached) && (iterator->getIndex() > pos)) |
| { |
| // Process any found data |
| |
| // If we didn't find a breakpoint split at the current position |
| |
| if (breakIndex == 0) |
| { |
| breakIndex = iterator->getIndex() - 1; |
| } |
| |
| // Trim blank space from the start of the next line |
| |
| CStringIterator *breakIterator = newStringIterator(); |
| |
| if (breakIterator->moveTo(breakIndex + 1)) |
| { |
| while (breakIterator->getChar() == ' ') |
| { |
| if (breakIterator->moveToNext()) |
| { |
| breakIndex++; |
| } |
| else |
| { |
| break; |
| } |
| } |
| } |
| |
| delete breakIterator; |
| |
| // Add the start of the next line to the vector |
| |
| pos = breakIndex + 1; |
| m_linePositions.push_back(pos); |
| |
| // Is this the longest line observed so far? |
| |
| if (lineWidth > m_textPixelWidth) |
| { |
| m_textPixelWidth = lineWidth; |
| |
| // Push the description of the line into the longest lines |
| // vector (note that we store the index in m_linePositions that |
| // refers to the start of the line, *not* the position of the |
| // line in the char array) |
| |
| LongestLine line; |
| line.index = m_linePositions.size() - 2; |
| line.width = lineWidth; |
| m_longestLines.push_back(line); |
| } |
| } |
| else if (!endReached) |
| { |
| // Add a blank row if we're not at the end of the string |
| |
| pos++; |
| m_linePositions.push_back(pos); |
| } |
| } |
| |
| // Add marker indicating end of text |
| // If we reached the end of the text, append the stopping point |
| |
| if ((unsigned int)m_linePositions[m_linePositions.size() - 1] != getLength() + 1) |
| { |
| m_linePositions.push_back(getLength()); |
| } |
| |
| delete iterator; |
| |
| // Calculate the total height of the text |
| |
| m_textPixelHeight = getLineCount() * (m_font->getHeight() + m_lineSpacing); |
| |
| // Ensure height is always at least one row |
| |
| if (m_textPixelHeight == 0) |
| { |
| m_textPixelHeight = m_font->getHeight() + m_lineSpacing; |
| } |
| } |
| |
| /** |
| * Get the index of the line of text that contains the specified index |
| * within the raw char array. |
| * |
| * @param index The index to locate within the wrapped lines of text. |
| * @return The number of the line of wrapped text that contains the |
| * specified index. |
| */ |
| |
| const int CText::getLineContainingCharIndex(const int index) const |
| { |
| // Early exit if there is no existing line data |
| |
| if (m_linePositions.size() == 0) |
| { |
| return 0; |
| } |
| |
| // Early exit if the character is in the last row |
| |
| if (index >= m_linePositions[m_linePositions.size() - 2]) |
| { |
| return m_linePositions.size() - 2; |
| } |
| |
| // Binary search the line vector for the line containing the supplied index |
| |
| int bottom = 0; |
| int top = m_linePositions.size() - 1; |
| int mid; |
| |
| while (bottom <= top) |
| { |
| // Standard binary search |
| |
| mid = (bottom + top) >> 1; |
| |
| if (index < m_linePositions[mid]) |
| { |
| // Index is somewhere in the lower search space |
| |
| top = mid - 1; |
| } |
| else if (index > m_linePositions[mid]) |
| { |
| // Index is somewhere in the upper search space |
| |
| bottom = mid + 1; |
| } |
| else if (index == m_linePositions[mid]) |
| { |
| // Located the index |
| |
| return mid; |
| } |
| |
| // Check to see if we've moved past the line that contains the index |
| // We have to do this because the index we're looking for can be within |
| // a line; it isn't necessarily the start of a line (which is what is |
| // stored in the m_linePositions vector) |
| |
| if (index > m_linePositions[top]) |
| { |
| // Search index falls within the line represented by the top position |
| |
| return top; |
| } |
| else if (index < m_linePositions[bottom]) |
| { |
| // Search index falls within the line represented by the bottom position |
| |
| return bottom - 1; |
| } |
| } |
| |
| // Line cannot be found |
| |
| return 0; |
| } |