diff --git a/apps/reader/utility.cpp b/apps/reader/utility.cpp index 8ed4cd4f0..51fdc043a 100644 --- a/apps/reader/utility.cpp +++ b/apps/reader/utility.cpp @@ -117,7 +117,7 @@ const char * EndOfPrintableWord(const char * word, const char * end) { UTF8Decoder decoder(word); CodePoint codePoint = decoder.nextCodePoint(); const char * result = word; - while (codePoint != '\n' && codePoint != ' ' && codePoint != '%' && codePoint != '$') { + while (codePoint != '\n' && codePoint != ' ' && codePoint != '%' && codePoint != '$' && codePoint != '\\') { result = decoder.stringPosition(); if (result >= end) { break; diff --git a/apps/reader/word_wrap_view.cpp b/apps/reader/word_wrap_view.cpp index c116e620e..f3a6d890b 100644 --- a/apps/reader/word_wrap_view.cpp +++ b/apps/reader/word_wrap_view.cpp @@ -14,15 +14,20 @@ WordWrapTextView::WordWrapTextView() : m_pageOffset(0), m_nextPageOffset(0), m_length(0), - m_isRichTextFile(false) // Value isn't important, it will change when the file is loaded + m_isRichTextFile(false), // Value isn't important, it will change when the file is loaded + m_lastPagesOffsetsIndex(0) { - + for (int i = 0; i < k_lastOffsetsBufferSize; i++) { + m_lastPagesOffsets[i] = -1; // -1 Means : no informations + } } void WordWrapTextView::nextPage() { if(m_nextPageOffset >= m_length) { return; } + m_lastPagesOffsets[m_lastPagesOffsetsIndex] = m_pageOffset; + m_lastPagesOffsetsIndex = (m_lastPagesOffsetsIndex + 1) % k_lastOffsetsBufferSize; m_pageOffset = m_nextPageOffset; markRectAsDirty(bounds()); } @@ -38,223 +43,342 @@ void WordWrapTextView::previousPage() { return; } + /* We check if we have available data in our buffer */ + int offsetToCheck = (m_lastPagesOffsetsIndex + k_lastOffsetsBufferSize - 1) % k_lastOffsetsBufferSize; + if (m_lastPagesOffsets[offsetToCheck] != -1) { + m_lastPagesOffsetsIndex = offsetToCheck; + m_pageOffset = m_lastPagesOffsets[offsetToCheck]; + m_lastPagesOffsets[offsetToCheck] = -1; + markRectAsDirty(bounds()); + return; + } + const int charWidth = m_font->glyphSize().width(); const int charHeight = m_font->glyphSize().height(); - const char * endOfFile = text() + m_length; - const char * endOfWord = text() + m_pageOffset; - const char * startOfWord = StartOfPrintableWord(endOfWord, text()); + const char * endOfWord = text() + m_pageOffset - 1; - KDSize textSize = KDSizeZero; + KDCoordinate baseline = charHeight; - KDPoint textEndPosition(m_frame.width() - k_margin, m_frame.height() - k_margin); + KDPoint textBottomEndPosition = KDPoint(m_frame.width() - k_margin, m_frame.height() - k_margin); + KDCoordinate lineHeight = charHeight; - while(startOfWord>=text()) { - startOfWord = StartOfPrintableWord(endOfWord-1, text()); - //endOfWord = EndOfPrintableWord(startOfWord, endOfFile); + while(endOfWord >= text()) { + // 1. Skip whitespaces and line jumps + while(endOfWord >= text() && (*endOfWord == ' ' || *endOfWord == '\n')) { + if(*endOfWord == '\n') { + textBottomEndPosition = KDPoint(m_frame.width() - k_margin, textBottomEndPosition.y() - lineHeight); + lineHeight = charHeight; + // We check if we must change page + if (textBottomEndPosition.y() - lineHeight <= k_margin) { + break; + } + } else { + textBottomEndPosition = KDPoint(textBottomEndPosition.x() - charWidth, textBottomEndPosition.y()); + } + endOfWord--; + } + + // 3. If word is a color change + if (*endOfWord == '%' && *(endOfWord - 1) != '\\') { + const char * startOfWord = endOfWord - 2; + while (*startOfWord != '%') { + startOfWord--; + } - if (*startOfWord == '%') { if (updateTextColorBackward(startOfWord)) { - endOfWord = startOfWord - 1; + endOfWord = startOfWord - 1; // Update next endOfWord continue; + } else { + // TODO: print error } } - if (*endOfWord == '$') { - startOfWord = endOfWord - 1; - while (*startOfWord != '$') { - if (startOfWord < text()) { + KDSize textSize = KDSizeZero; + + // 4. If word is a mathematical expression + if (*endOfWord == '$' && *(endOfWord - 1) != '\\') { + // We go to the end of the expression + 1 + const char * expressionStart = --endOfWord; + while (*expressionStart != '$') { + if (expressionStart < text()) { break; // File isn't rightly formated } - startOfWord --; + expressionStart ++; } - startOfWord --; - TexParser parser = TexParser(startOfWord + 1, endOfWord - 2); - Poincare::Layout layout = parser.getLayout(); - textSize = layout.layoutSize(); + TexParser parser = TexParser(expressionStart, endOfWord); + Layout layout = parser.getLayout(); + + KDCoordinate layoutBaseline = layout.baseline(); + + // We check if we must change baseline + if (layoutBaseline > baseline) { + baseline = layoutBaseline; + } + + KDSize layoutSize = layout.layoutSize(); + textSize = KDSize(layoutSize.width(), layoutSize.height() + baseline - layoutBaseline); + + endOfWord = expressionStart; } + + // 5. Else it's text else { - if (*startOfWord == '\\' || *(startOfWord + 1) == '$') { - textSize = m_font->stringSizeUntil(startOfWord + 1, endOfWord); - } - else { - textSize = m_font->stringSizeUntil(startOfWord, endOfWord); + // We go to the start of the word + const char * startOfWord = StartOfPrintableWord(endOfWord, text()); + + textSize = m_font->stringSizeUntil(startOfWord, endOfWord + 1); + + endOfWord = startOfWord; + } + + // 6. We check if we must change line + if (textBottomEndPosition.x() - textSize.width() <= k_margin) { + textBottomEndPosition = KDPoint(m_frame.width() - k_margin, textBottomEndPosition.y() - lineHeight); + lineHeight = 0; + // We will check if we must change page below + } + textBottomEndPosition = KDPoint(textBottomEndPosition.x() - textSize.width(), textBottomEndPosition.y()); + + // 7. We update height of the line if needed + if (textSize.height() > lineHeight) { + lineHeight = textSize.height(); + // We check if we must change page + if (textBottomEndPosition.y() - lineHeight <= k_margin) { + break; } } - KDPoint textStartPosition = KDPoint(textEndPosition.x()-textSize.width(), textEndPosition.y()); - if(textStartPosition.x() < k_margin) { - textEndPosition = KDPoint(m_frame.width() - k_margin, textEndPosition.y() - charHeight); - textStartPosition = KDPoint(textEndPosition.x() - textSize.width(), textEndPosition.y()); - } - if(textEndPosition.y() - textSize.height() < k_margin) { - break; - } - - --startOfWord; - while(startOfWord >= text() && (*startOfWord == ' ' || *startOfWord == '\n')) { - if(*startOfWord == ' ') { - textStartPosition = KDPoint(textStartPosition.x() - charWidth, textStartPosition.y()); - } - else { - textStartPosition = KDPoint(m_frame.width() - k_margin, textStartPosition.y() - charHeight); - } - --startOfWord; - } - - if(textStartPosition.y() < k_margin) { // If out of page, quit - break; - } - - if(textStartPosition.y() != textEndPosition.y()) { // If line changed, x is at start of line - textStartPosition = KDPoint(m_frame.width() - k_margin, textStartPosition.y()); - } - if(textStartPosition.x() < k_margin) { // Go to line if left overflow - textStartPosition = KDPoint(m_frame.width() - k_margin, textStartPosition.y() - charHeight); - } - - textEndPosition = textStartPosition; - endOfWord = startOfWord + 1; + endOfWord -= 1; } - if(startOfWord + 1 == text()) { - m_pageOffset = 0; - } - else { - m_pageOffset = EndOfPrintableWord(startOfWord, endOfFile) - text() + 1; + + if (endOfWord + 1 == text()) { + m_pageOffset = 0; + } else { + m_pageOffset = endOfWord - text(); } + + // Because we ask for a redraw, m_endTextPosition must auto update at the bottom of drawRect... markRectAsDirty(bounds()); } void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { ctx->fillRect(KDRect(0, 0, bounds().width(), bounds().height()), m_backgroundColor); - const char * endOfFile = text() + m_length; - const char * startOfWord = text() + m_pageOffset; - const char * endOfWord; - - if (*startOfWord != '$') { - endOfWord = EndOfPrintableWord(startOfWord, endOfFile); - } // Else we don't need to update endOfWord - - KDPoint textPosition(k_margin, k_margin); - - const int wordMaxLength = 128; - char word[wordMaxLength]; - - Poincare::Layout layout; - enum class ToDraw { Text, - Expression, - Nothing + Expression }; - ToDraw toDraw = ToDraw::Text; + bool endOfPage = false; + + const char * endOfFile = text() + m_length; + const char * startOfWord = text() + m_pageOffset; const int charWidth = m_font->glyphSize().width(); const int charHeight = m_font->glyphSize().height(); - int nextLineOffset = charHeight; + const int wordMaxLength = (m_frame.width() - 2*k_margin ) / charWidth; + char word[wordMaxLength]; - KDSize textSize = KDSizeZero; + Layout layout; + KDPoint textPosition = KDPoint(k_margin, k_margin); + + while (!endOfPage && startOfWord < endOfFile) { + // We process line by line - while(startOfWord < endOfFile) { + const char * firstReadIndex = startOfWord; - if (*startOfWord == '%') { // Look for color keyword (ex '%bl%') - if (updateTextColorForward(startOfWord)) { - startOfWord = endOfWord + 1; - endOfWord = EndOfPrintableWord(startOfWord, endOfFile); + // 1. We compute the size of what we are going to draw and the baseline + KDSize lineSize = KDSize(0, charHeight); + KDCoordinate baseline = charHeight / 2; + + while (firstReadIndex < endOfFile) { + + KDSize textSize = KDSizeZero; + + // 1.1. And we check if we are at the end of the line + if(*firstReadIndex == '\n') { + break; + } + + // 1.2. Check if we are in a color change + if (*firstReadIndex == '%') { // We assume each '%' non-escaped is announcing a color change // TODO : check file is rightly formated + // We go to the end of the color change + 1 + do { + firstReadIndex ++; + } while (*firstReadIndex != '%'); + firstReadIndex ++; continue; } + + // 1.3. Check if we are in a math expression + if (*firstReadIndex == '$') { + // We go to the end of the expression + 1 + const char * expressionStart = ++firstReadIndex; + while (*firstReadIndex != '$') { + if (firstReadIndex > endOfFile) { + break; // File isn't rightly formated + } + firstReadIndex ++; + } + + TexParser parser = TexParser(expressionStart, firstReadIndex); + Layout layout = parser.getLayout(); + + KDCoordinate layoutBaseline = layout.baseline(); + // We check if we must change baseline + if (layoutBaseline > baseline) { + baseline = layoutBaseline; + } + + KDSize layoutSize = layout.layoutSize(); + textSize = KDSize(layoutSize.width(), layoutSize.height() + baseline - layoutBaseline); + + firstReadIndex ++; + } + + // 1.4. Else it's text + else { + if ((*firstReadIndex == '\\' && *(firstReadIndex + 1) == '$') || (*firstReadIndex == '\\' && *(firstReadIndex + 1) == '%')) { // We escape '$' and '%' if needed + firstReadIndex ++; + } + + const char * endOfWord = EndOfPrintableWord(firstReadIndex + 1, endOfFile); + + textSize = m_font->stringSizeUntil(firstReadIndex, endOfWord); + + firstReadIndex = endOfWord; + } + + // 1.5. We update size + int newWidth = lineSize.width() + textSize.width(); + // We check if the new text fit on the line + if (newWidth > m_frame.width() - 2 * k_margin) { + break; + } + + int newHeight; + if (lineSize.height() > textSize.height()) { + newHeight = lineSize.height(); + } else { + newHeight = textSize.height(); + // We check if all the content can be displayed + if (textPosition.y() + newHeight > bounds().height() - k_margin) { + endOfPage = true; + break; + } + } + lineSize = KDSize(lineSize.width() + textSize.width(), newHeight); + + // 1.6. We go to the next word + while (*firstReadIndex == ' ') { + lineSize = KDSize(lineSize.width() + charWidth, lineSize.height()); + ++firstReadIndex; + } } - if (*startOfWord == '$') { // Look for expression - endOfWord = startOfWord + 1; - while (*endOfWord != '$') { - if (endOfWord > endOfFile) { - break; // If we are here, it's bad... + if (endOfPage) { + break; + } + + // 2. And now... we read the line again to draw it ! + while (startOfWord < endOfFile) { + + //2.1. We check if we are at the end of the line + if (*startOfWord == '\n') { + startOfWord++; + textPosition = KDPoint(k_margin, textPosition.y() + lineSize.height()); + break; + // We aren't supposed to be at the end of the page, else the loop on top would have stopped drawing + } + + + const char * endOfWord; + + // 2.2. Check if we are in a color change + if (*startOfWord == '%') { + if (updateTextColorForward(startOfWord)) { + startOfWord += 2; // We can add at least 2 ('%' + the color first char) + while (*startOfWord != '%') { + startOfWord ++; + } + startOfWord ++; + continue; + } + else { + // TODO: Print exception + } + } + + // 2.3. Check what we are going to draw and his size + + KDSize textSize = KDSizeZero; + ToDraw toDraw; + + // 2.3.1. Check if we are in a math expression + if (*startOfWord == '$') { + endOfWord = startOfWord + 1; + while (*endOfWord != '$') { + if (endOfWord > endOfFile) { + break; // File isn't rightly formated + } + endOfWord ++; } endOfWord ++; + + TexParser parser = TexParser(startOfWord + 1, endOfWord - 1); + layout = parser.getLayout(); + textSize = layout.layoutSize(); + + toDraw = ToDraw::Expression; } - endOfWord ++; - TexParser parser = TexParser(startOfWord + 1, endOfWord - 1); - layout = parser.getLayout(); - textSize = layout.layoutSize(); - toDraw = ToDraw::Expression; - } - else { - if (*startOfWord == '\\' || *(startOfWord + 1) == '$') { - startOfWord ++; + // 2.3.2 Else it's text + else { + if ((*startOfWord == '\\' && *(startOfWord + 1) == '$') || (*startOfWord == '\\' && *(startOfWord + 1) == '%')) { + startOfWord ++; + } + endOfWord = EndOfPrintableWord(startOfWord + 1, endOfFile); + textSize = m_font->stringSizeUntil(startOfWord, endOfWord); + stringNCopy(word, wordMaxLength, startOfWord, endOfWord-startOfWord); + toDraw = ToDraw::Text; } - textSize = m_font->stringSizeUntil(startOfWord, endOfWord); - stringNCopy(word, wordMaxLength, startOfWord, endOfWord-startOfWord); - toDraw = ToDraw::Text; - } - KDPoint nextTextPosition = KDPoint(textPosition.x()+textSize.width(), textPosition.y()); - - if(nextTextPosition.x() > m_frame.width() - k_margin) { // Right overflow - textPosition = KDPoint(k_margin, textPosition.y() + nextLineOffset); - nextTextPosition = KDPoint(k_margin + textSize.width(), textPosition.y()); - nextLineOffset = charHeight; - } - if (nextLineOffset < textSize.height()) { - nextLineOffset = textSize.height(); - } + // 2.4 We decide where to draw and if we must change line + KDPoint endTextPosition = KDPoint(textPosition.x() + textSize.width(), textPosition.y()); + + // 2.4.1. Check if we need to go to the next line + if(endTextPosition.x() > m_frame.width() - k_margin) { + textPosition = KDPoint(k_margin, textPosition.y() + lineSize.height()); + break; + } - if(textPosition.y() + textSize.height() > m_frame.height() - k_margin) { // Bottom overflow - break; - } - - if (toDraw == ToDraw::Expression) { - layout.draw(ctx, textPosition, m_textColor, m_backgroundColor); - } - else if (toDraw == ToDraw::Text) { - ctx->drawString(word, textPosition, m_font, m_textColor, m_backgroundColor); - } - - while(*endOfWord == ' ' || *endOfWord == '\n') { - if(*endOfWord == ' ') { - nextTextPosition = KDPoint(nextTextPosition.x() + charWidth, nextTextPosition.y()); + // 2.5. Now we draw ! + if (toDraw == ToDraw::Expression) { + KDPoint position = KDPoint(textPosition.x(), textPosition.y() + baseline - layout.baseline()); + layout.draw(ctx, position, m_textColor, m_backgroundColor); } else { - nextTextPosition = KDPoint(k_margin, nextTextPosition.y() + nextLineOffset); - nextLineOffset = charHeight; + KDPoint position = KDPoint(textPosition.x(), textPosition.y() + baseline - charHeight / 2); + ctx->drawString(word, position, m_font, m_textColor, m_backgroundColor); } - ++endOfWord; - } - //We must change value of startOfWord now to avoid having - //two times the same word if the break below is used - startOfWord = endOfWord; + // 2.6. Update the position + textPosition = endTextPosition; - if (endOfWord >= endOfFile) { - break; - } - - if(nextTextPosition.y() + textSize.height() > m_frame.height() - k_margin) { // If out of page, quit - break; - } - if(nextTextPosition.y() != textPosition.y()) { // If line changed, x is at start of line - nextTextPosition = KDPoint(k_margin, nextTextPosition.y()); - } - if(nextTextPosition.x() > m_frame.width() - k_margin) { // Go to line if right overflow - nextTextPosition = KDPoint(k_margin, nextTextPosition.y() + nextLineOffset); - nextLineOffset = charHeight; - } - - textPosition = nextTextPosition; - - if (*startOfWord != '$') { - endOfWord = EndOfPrintableWord(startOfWord+1, endOfFile); - } // Else we don't need to update endOfWord + // 2.7. And we go to the next word + while (*endOfWord == ' ') { + endOfWord++; + textPosition = KDPoint(textPosition.x() + charWidth, textPosition.y()); + } + startOfWord = endOfWord; + } } - m_nextPageOffset = startOfWord - text(); -}; +} BookSave WordWrapTextView::getBookSave() const { return { @@ -270,9 +394,9 @@ void WordWrapTextView::setBookSave(BookSave save) { bool WordWrapTextView::updateTextColorForward(const char * colorStart) const { - if (*(colorStart + 1) == '\\') { + if (*(colorStart + 1) == '\\' && (*(colorStart + 3) == '%' || *(colorStart + 4) == '%')) { m_textColor = Palette::PrimaryText; - return (*(colorStart + 3) == '%' || *(colorStart + 4) == '%'); + return true; } int keySize = 1; diff --git a/apps/reader/word_wrap_view.h b/apps/reader/word_wrap_view.h index a3f3c87eb..fca07759e 100644 --- a/apps/reader/word_wrap_view.h +++ b/apps/reader/word_wrap_view.h @@ -25,11 +25,19 @@ private: bool updateTextColorForward(const char * colorStart) const; bool updateTextColorBackward(const char * colorStart) const; static const int k_margin = 10; + static const int k_lastOffsetsBufferSize = 10; int m_pageOffset; mutable int m_nextPageOffset; int m_length; bool m_isRichTextFile; mutable KDColor m_textColor; + /* + * Beacause the text that we draw can be of different sizes, we can't + * exactly know where the last page starts. + * So we store into a buffer (a cyclic stack) the offsets of the last pages. + */ + int m_lastPagesOffsets[k_lastOffsetsBufferSize]; + int m_lastPagesOffsetsIndex; }; } diff --git a/kandinsky/fonts/LargeFont.ttf b/kandinsky/fonts/LargeFont.ttf index 51a39d85a..5446c8134 100644 Binary files a/kandinsky/fonts/LargeFont.ttf and b/kandinsky/fonts/LargeFont.ttf differ diff --git a/kandinsky/fonts/SmallFont.ttf b/kandinsky/fonts/SmallFont.ttf index f7ee64b4e..9df343016 100644 Binary files a/kandinsky/fonts/SmallFont.ttf and b/kandinsky/fonts/SmallFont.ttf differ