[escher/src/text_area] Add char limit in text_area line

Change-Id: I9284936f0202d788edc785aa3f7c82b45ab34cf5
This commit is contained in:
Hugo Saint-Vignes
2020-09-03 17:19:59 +02:00
committed by Émilie Feral
parent 51002066e9
commit b92c819ea2
8 changed files with 122 additions and 13 deletions

View File

@@ -138,6 +138,7 @@ private:
TextAreaDelegate * m_delegate;
// Due to rect size limitation, the editor cannot display more than 1800 lines
constexpr static int k_maxLines = 999;
constexpr static int k_maxLineChars = 3000;
};
#endif

View File

@@ -47,24 +47,79 @@ bool TextArea::handleEventWithText(const char * text, bool indentation, bool for
* indentation, stop here. */
int spacesCount = 0;
int totalIndentationSize = 0;
int textLen = strlen(text);
int addedTextLength = strlen(text);
size_t previousTextLength = contentView()->getText()->textLength();
char * insertionPosition = const_cast<char *>(cursorLocation());
const char * textAreaBuffer = contentView()->text();
if (indentation) {
// Compute the indentation
spacesCount = indentationBeforeCursor();
const char * textAreaBuffer = contentView()->text();
if (insertionPosition > textAreaBuffer && UTF8Helper::PreviousCodePointIs(textAreaBuffer, insertionPosition, ':')) {
spacesCount += k_indentationSpaces;
}
// Check the text will not overflow the buffer
totalIndentationSize = UTF8Helper::CountOccurrences(text, '\n') * spacesCount;
if (contentView()->getText()->textLength() + textLen + totalIndentationSize >= contentView()->getText()->bufferSize()) {
if (previousTextLength + addedTextLength + totalIndentationSize >= contentView()->getText()->bufferSize()) {
return false;
}
}
// Check the text will not overflow the max number of lines
if (contentView()->getText()->textLineTotal() + UTF8Helper::CountOccurrences(text, '\n') >= k_maxLines) {
/* KDCoordinate is a int16. We must limit the number of characters per line,
* and lines per scripts, otherwise the line rects or content rect
* height/width can overflow int16, which results in weird visual effects.*/
// 1 - Number of Characters per line :
if (previousTextLength + addedTextLength > k_maxLineChars) {
/* Only check for long lines in long scripts. PreviousTextLength and
* addedTextLength being greater than the actual number of glyphs is not an
* issue here. After insertion, text buffer will have this structure :
* ".../n"+"before"+"inserted1"+("/n.../n")?+"inserted2"+"after"+"\n..."
* Lengths : b ib ia a
* As maxBufferSize is lower than k_maxLineChars, there is no need to check
* for inserted lines between "\n...\n" */
static_assert(TextField::maxBufferSize() < k_maxLineChars, "Pasting text might cause content rect overflow.");
// Counting line text lengths before and after insertion.
int b = 0;
int a = 0;
UTF8Helper::countGlyphsInLine(textAreaBuffer, &b, &a, insertionPosition);
if (a + b + addedTextLength > k_maxLineChars) {
/* Overflow expected, depending on '/n' code point presence and position.
* Counting : Glyphs inserted before first '/n' : ib
* Glyphs inserted after last '/n' : ia
* Number of '/n' : n */
int glyphCount[3] = {0, 0, 0};
UTF8Helper::PerformAtCodePoints(text, '\n',
[](int, void * intArray, int, int) {
// '\n' found, Increment n
int * n = (int *)intArray + 2;
*n = *n + 1;
// Reset ia
int * ia = (int *)intArray + 1;
*ia = 0;
},
[](int, void * intArray, int, int) {
if (((int *)intArray)[2] == 0) {
// While no '\n' found, increment ib
int * ib = (int *)intArray;
*ib = *ib + 1;
} else {
// Increment ia
int * ia = (int *)intArray + 1;
*ia = *ia + 1;
}
},
&glyphCount, 0, 0);
// Insertion is not possible if one of the produced line is too long.
if ((glyphCount[2] == 0 && a + glyphCount[0] + b > k_maxLineChars) || b + glyphCount[0] > k_maxLineChars || a + glyphCount[1] > k_maxLineChars) {
return false;
}
}
}
// 2 - Total number of line :
if (previousTextLength + addedTextLength > k_maxLines && contentView()->getText()->textLineTotal() + UTF8Helper::CountOccurrences(text, '\n') > k_maxLines) {
// Only check for overflowed lines in long scripts to save computation
return false;
}
@@ -89,9 +144,9 @@ bool TextArea::handleEventWithText(const char * text, bool indentation, bool for
UCodePointNull,
true,
nullptr,
insertionPosition + textLen);
insertionPosition + addedTextLength);
}
const char * endOfInsertedText = insertionPosition + textLen + totalIndentationSize;
const char * endOfInsertedText = insertionPosition + addedTextLength + totalIndentationSize;
const char * cursorPositionInCommand = TextInputHelpers::CursorPositionInCommand(insertionPosition, endOfInsertedText);
// Remove the Empty code points
@@ -268,9 +323,16 @@ CodePoint TextArea::Text::removePreviousGlyph(char * * position) {
assert(m_buffer <= *position && *position < m_buffer + m_bufferSize);
CodePoint removedCodePoint = 0;
int removedSize = UTF8Helper::RemovePreviousGlyph(m_buffer, *position, &removedCodePoint);
assert(removedSize > 0);
int removedSize = 0;
if (UTF8Helper::PreviousCodePoint(m_buffer, *position) == '\n') {
// See comments in handleEventWithText about max number of glyphs per line
removedCodePoint = '\n';
// removeText will handle max number of glyphs per line
removedSize = removeText(*position-1, *position);
} else {
removedSize = UTF8Helper::RemovePreviousGlyph(m_buffer, *position, &removedCodePoint);
assert(removedSize > 0);
}
// Set the new cursor position
*position = *position - removedSize;
return removedCodePoint;
@@ -288,6 +350,24 @@ size_t TextArea::Text::removeText(const char * start, const char * end) {
return 0;
}
/* Removing text can increase line length. See comments in handleEventWithText
* about max number of glyphs per line. */
if (textLength() - delta >= k_maxLineChars) {
/* Only check for line length on long enough scripts. TextLength() and delta
* being greater than the actual number of glyphs is not an issue here. */
// Counting text lengths between previous and last '/n' (non removed).
int b = 0;
int a = 0;
UTF8Helper::countGlyphsInLine(text(), &b, &a, start, end);
if (a + b > k_maxLineChars) {
// Resulting line would exceed limits, no text is removed
// TODO error message: Add Message to explain failure to remove text
return 0;
}
}
for (size_t index = src - m_buffer; index < m_bufferSize; index++) {
*dst = *src;
if (*src == 0) {
@@ -551,6 +631,9 @@ KDRect TextArea::ContentView::glyphFrameAtPosition(const char * text, const char
assert(found);
(void) found;
// Check for KDCoordinate overflow
assert(x < KDCOORDINATE_MAX - glyphSize.width() && p.line() * glyphSize.height() < KDCOORDINATE_MAX - glyphSize.height());
return KDRect(
x,
p.line() * glyphSize.height(),

View File

@@ -145,7 +145,9 @@ void TextInput::scrollToCursor() {
* In order to avoid requiring two layouts, we allow overscrolling in
* scrollToContentRect, and the last layout of the scroll view corrects the
* size of the scroll view only once. */
scrollToContentRect(contentView()->cursorRect(), true);
KDRect cursorRect = contentView()->cursorRect();
assert(cursorRect.top() >= 0 && cursorRect.right() >= 0 && cursorRect.bottom() >= 0 && cursorRect.left() >= 0);
scrollToContentRect(cursorRect, true);
}
void TextInput::deleteSelection() {
@@ -201,7 +203,7 @@ bool TextInput::moveCursorLeft(int step) {
}
i++;
}
// true is returned if there was at least one successful cursor mouvement
// true is returned if there was at least one successful cursor movement
return (i > 1 || canMove);
}
@@ -218,7 +220,7 @@ bool TextInput::moveCursorRight(int step) {
}
i++;
}
// true is returned if there was at least one successful cursor mouvement
// true is returned if there was at least one successful cursor movement
return (i > 1 || canMove);
}

View File

@@ -89,6 +89,9 @@ const char * BeginningOfWord(const char * text, const char * word);
// Returns the position of the first following char ' ', '\n' or 0
const char * EndOfWord(const char * word);
// On a line, count number of glyphs before and after locations
void countGlyphsInLine(const char * text, int * before, int * after, const char * beforeLocation, const char *afterLocation = nullptr);
};
#endif

View File

@@ -407,4 +407,18 @@ const char * EndOfWord(const char * word) {
return result;
}
void countGlyphsInLine(const char * text, int * before, int * after, const char * beforeLocation, const char *afterLocation) {
UTF8Helper::CodePointAction countGlyph = [](int, void * glyphCount, int, int) {
int * castedCount = (int *) glyphCount;
*castedCount = *castedCount + 1;
};
// Count glyphs before
UTF8Helper::PerformAtCodePoints(text, UCodePointLineFeed, nullptr, countGlyph, before, 0, 0, UCodePointLineFeed, false, beforeLocation);
if (afterLocation == nullptr) {
afterLocation = beforeLocation;
}
// Count glyphs after
UTF8Helper::PerformAtCodePoints(afterLocation, UCodePointLineFeed, nullptr, countGlyph, after, 0, 0, UCodePointLineFeed);
}
}

View File

@@ -28,6 +28,7 @@ KDPoint KDContext::pushOrPullString(const char * text, KDPoint p, const KDFont *
while (codePoint != UCodePointNull && (maxByteLength < 0 || codePointPointer < text + maxByteLength)) {
codePointPointer = decoder.stringPosition();
if (codePoint == UCodePointLineFeed) {
assert(position.y() < KDCOORDINATE_MAX - glyphSize.height());
position = KDPoint(0, position.y() + glyphSize.height());
codePoint = decoder.nextCodePoint();
} else if (codePoint == UCodePointTabulation) {

View File

@@ -30,6 +30,7 @@ KDSize KDFont::stringSizeUntil(const char * text, const char * limit) const {
currentStringPosition = decoder.stringPosition();
codePoint = decoder.nextCodePoint();
}
assert(stringSize.width() >= 0 && stringSize.height() >= 0);
return stringSize;
}

View File

@@ -1,6 +1,10 @@
#include <kandinsky/point.h>
#include <assert.h>
KDPoint KDPoint::translatedBy(KDPoint other) const {
assert((other.x() >= 0 && m_x <= KDCOORDINATE_MAX - other.x()) || (other.x() < 0 && m_x >= KDCOORDINATE_MIN - other.x()));
assert((other.y() >= 0 && m_y <= KDCOORDINATE_MAX - other.y()) || (other.y() < 0 && m_y >= KDCOORDINATE_MIN - other.y()));
return KDPoint(m_x+other.x(), m_y+other.y());
}