diff --git a/apps/reader/README.md b/apps/reader/README.md new file mode 100644 index 000000000..a37dafd7a --- /dev/null +++ b/apps/reader/README.md @@ -0,0 +1,27 @@ +# Thanks +Thanks to [Gabriel79](https://github.com/Gabriel79) for the original reader app, his source code available [here](https://github.com/Gabriel79/OmegaWithReaderTutorial) and the [tutorial](https://www.codingame.com/playgrounds/55846/reader-faire-une-application-pour-omega-sur-numworks/introduction) to code it ! + +--- + +# Rich text format +Reader app supports now a rich text format : + + * `$` around a mathematical expression **without spaces** to render it + * `%` around a color-code (see below) to change the color of the text +### Color codes : +|code|color| +| --:| ---:| +|`%d%`|Default color| +|`%r%`|Red| +|`%rl%`|Light red| +|`%m%`|Magenta| +|`%t%`|Turquoise| +|`%pk%`|Pink| +|`%pp%`|Purple| +|`%b%`|Blue| +|`%bl%`|Light blue| +|`%br%`|Brown| +|`%o%`|Orange| +|`%g%`|Green| +|`%gl%`|Light green| +|`%c%`|Cyan| \ No newline at end of file diff --git a/apps/reader/read_book_controller.cpp b/apps/reader/read_book_controller.cpp index 144385918..622389a98 100644 --- a/apps/reader/read_book_controller.cpp +++ b/apps/reader/read_book_controller.cpp @@ -27,19 +27,21 @@ bool ReadBookController::handleEvent(Ion::Events::Event event) { m_readerView.previousPage(); return true; } - if(event == Ion::Events::Back || event == Ion::Events::Home) { - savePosition(); - } return false; } +void ReadBookController::viewDidDisappear() { + savePosition(); +} + void ReadBookController::savePosition() const { - int pageOffset = m_readerView.getPageOffset(); - Ion::Storage::Record::ErrorStatus status = Ion::Storage::sharedStorage()->createRecordWithFullName(m_file->name, &pageOffset, sizeof(pageOffset)); + BookSave save = m_readerView.getBookSave(); + + Ion::Storage::Record::ErrorStatus status = Ion::Storage::sharedStorage()->createRecordWithFullName(m_file->name, &save, sizeof(save)); if(Ion::Storage::Record::ErrorStatus::NameTaken == status) { Ion::Storage::Record::Data data; - data.buffer = &pageOffset; - data.size = sizeof(pageOffset); + data.buffer = &save; + data.size = sizeof(save); status = Ion::Storage::sharedStorage()->recordNamed(m_file->name).setValue(data); } } @@ -47,11 +49,14 @@ void ReadBookController::savePosition() const { void ReadBookController::loadPosition() { Ion::Storage::Record r = Ion::Storage::sharedStorage()->recordNamed(m_file->name); if(Ion::Storage::sharedStorage()->hasRecord(r)) { - int pageOffset = *(static_cast(r.value().buffer)); - m_readerView.setPageOffset(pageOffset); + BookSave save = *(static_cast(r.value().buffer)); + m_readerView.setBookSave(save); } else { - m_readerView.setPageOffset(0); + m_readerView.setBookSave({ + 0, + Palette::PrimaryText + }); } } diff --git a/apps/reader/read_book_controller.h b/apps/reader/read_book_controller.h index 77d4f57f0..0bd006dc3 100644 --- a/apps/reader/read_book_controller.h +++ b/apps/reader/read_book_controller.h @@ -11,10 +11,9 @@ class ReadBookController : public ViewController { public: ReadBookController(Responder * parentResponder); View * view() override; - void setBook(const External::Archive::File& file); bool handleEvent(Ion::Events::Event event) override; - + void viewDidDisappear() override; void savePosition() const; void loadPosition(); private: diff --git a/apps/reader/utility.cpp b/apps/reader/utility.cpp index 123fe3920..d941039cd 100644 --- a/apps/reader/utility.cpp +++ b/apps/reader/utility.cpp @@ -110,4 +110,21 @@ int filesWithExtension(const char* extension, External::Archive::File* files, in } #endif +const char * EndOfPrintableWord(const char * word, const char * end) { + if (word == end) { + return word; + } + UTF8Decoder decoder(word); + CodePoint codePoint = decoder.nextCodePoint(); + const char * result = word; + while (codePoint != '\n' && codePoint != ' ' && codePoint != '%') { + result = decoder.stringPosition(); + if (result >= end) { + break; + } + codePoint = decoder.nextCodePoint(); + } + return result; +} + } \ No newline at end of file diff --git a/apps/reader/utility.h b/apps/reader/utility.h index 36e1d1b4b..55a6e8c33 100644 --- a/apps/reader/utility.h +++ b/apps/reader/utility.h @@ -2,6 +2,8 @@ #define __UTILITY_H__ #include +#include +#include namespace Reader { @@ -9,6 +11,7 @@ namespace Reader bool stringEndsWith(const char* str, const char* end); int filesWithExtension(const char* extension, External::Archive::File* files, int filesSize); void stringNCopy(char* dest, int max, const char* src, int len); - +const char * EndOfPrintableWord(const char * word, const char * end); + } #endif \ No newline at end of file diff --git a/apps/reader/word_wrap_view.cpp b/apps/reader/word_wrap_view.cpp index c8cb708cf..49ea133ab 100644 --- a/apps/reader/word_wrap_view.cpp +++ b/apps/reader/word_wrap_view.cpp @@ -1,10 +1,22 @@ #include "word_wrap_view.h" #include "utility.h" +#include +#include "../shared/poincare_helpers.h" +#include namespace Reader { +WordWrapTextView::WordWrapTextView() : + PointerTextView(GlobalPreferences::sharedGlobalPreferences()->font()), + m_pageOffset(0), + m_nextPageOffset(0), + m_length(0) +{ + +} + void WordWrapTextView::nextPage() { if(m_nextPageOffset >= m_length) { return; @@ -29,12 +41,35 @@ void WordWrapTextView::previousPage() { const char * endOfWord = text() + m_pageOffset - 1; const char * startOfWord = UTF8Helper::BeginningOfWord(text(), endOfWord); + KDSize textSize = KDSizeZero; + KDPoint textEndPosition(m_frame.width() - k_margin, m_frame.height() - k_margin); while(startOfWord>=text()) { startOfWord = UTF8Helper::BeginningOfWord(text(), endOfWord); - endOfWord = UTF8Helper::EndOfWord(startOfWord); - KDSize textSize = m_font->stringSizeUntil(startOfWord, endOfWord); + endOfWord = UTF8Helper::EndOfWord(startOfWord); + + if (*startOfWord == '%') { + if (updateTextColorBackward(startOfWord)) { + endOfWord = startOfWord - 1; + continue; + } + } + if (*startOfWord == '$' && *(endOfWord-1) == '$') { + const int wordMaxLength = 128; + char word[wordMaxLength]; + stringNCopy(word, wordMaxLength, startOfWord + 1, endOfWord-startOfWord-2); + Poincare::Expression expr = Poincare::Expression::Parse(word, nullptr); + if (expr.isUninitialized()) { + expr = Poincare::Undefined::Builder(); + } + Poincare::Layout layout = Shared::PoincareHelpers::CreateLayout(expr); + textSize = layout.layoutSize(); + + } + else { + textSize = m_font->stringSizeUntil(startOfWord, endOfWord); + } KDPoint textStartPosition = KDPoint(textEndPosition.x()-textSize.width(), textEndPosition.y()); if(textStartPosition.x() < k_margin) { @@ -84,37 +119,85 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { const char * endOfFile = text() + m_length; const char * startOfWord = text() + m_pageOffset; - const char * endOfWord = UTF8Helper::EndOfWord(startOfWord); + const char * endOfWord = EndOfPrintableWord(startOfWord, endOfFile); KDPoint textPosition(k_margin, k_margin); const int wordMaxLength = 128; char word[wordMaxLength]; + Poincare::Layout layout; + + enum class ToDraw { + Text, + Expression, + Nothing + }; + + ToDraw toDraw = ToDraw::Text; + const int charWidth = m_font->glyphSize().width(); const int charHeight = m_font->glyphSize().height(); + int nextLineOffset = charHeight; + + KDSize textSize = KDSizeZero; + + while(startOfWord < endOfFile) { - KDSize textSize = m_font->stringSizeUntil(startOfWord, endOfWord); + + if (*startOfWord == '%') { // Look for color keyword (ex '%bl%') + if (updateTextColorForward(startOfWord)) { + startOfWord = endOfWord + 1; + endOfWord = EndOfPrintableWord(startOfWord, endOfFile); + continue; + } + } + + if (*startOfWord == '$' && *(endOfWord-1) == '$') { // Look for expression + stringNCopy(word, wordMaxLength, startOfWord + 1, endOfWord-startOfWord-2); + Poincare::Expression expr = Poincare::Expression::Parse(word, nullptr); + if (expr.isUninitialized()) { + expr = Poincare::Undefined::Builder(); + } + layout = Shared::PoincareHelpers::CreateLayout(expr); + textSize = layout.layoutSize(); + toDraw = ToDraw::Expression; + } + else { + 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() + textSize.height()); + textPosition = KDPoint(k_margin, textPosition.y() + nextLineOffset); nextTextPosition = KDPoint(k_margin + textSize.width(), textPosition.y()); + nextLineOffset = charHeight; + } + if (nextLineOffset < textSize.height()) { + nextLineOffset = textSize.height(); } if(textPosition.y() + textSize.height() > m_frame.height() - k_margin) { // Bottom overflow break; } - stringNCopy(word, wordMaxLength, startOfWord, endOfWord-startOfWord); - ctx->drawString(word, textPosition, m_font, m_textColor, m_backgroundColor); + if (toDraw == ToDraw::Expression) { + layout.draw(ctx, textPosition, m_textColor); + } + 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()); } else { - nextTextPosition = KDPoint(k_margin, nextTextPosition.y() + charHeight); + nextTextPosition = KDPoint(k_margin, nextTextPosition.y() + nextLineOffset); + nextLineOffset = charHeight; } ++endOfWord; } @@ -123,6 +206,10 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { //two times the same word if the break below is used startOfWord = endOfWord; + if (endOfWord >= endOfFile) { + break; + } + if(nextTextPosition.y() + textSize.height() > m_frame.height() - k_margin) { // If out of page, quit break; } @@ -130,22 +217,181 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { 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() + textSize.height()); + nextTextPosition = KDPoint(k_margin, nextTextPosition.y() + nextLineOffset); + nextLineOffset = charHeight; } textPosition = nextTextPosition; - endOfWord = UTF8Helper::EndOfWord(startOfWord); + endOfWord = EndOfPrintableWord(startOfWord+1, endOfFile); } m_nextPageOffset = startOfWord - text(); }; -int WordWrapTextView::getPageOffset() const { - return m_pageOffset; +BookSave WordWrapTextView::getBookSave() const { + return { + m_pageOffset, + m_textColor + }; } -void WordWrapTextView::setPageOffset(int o) { - m_pageOffset = o; +void WordWrapTextView::setBookSave(BookSave save) { + m_pageOffset = save.offset; + m_textColor = save.color; } -} \ No newline at end of file +bool WordWrapTextView::updateTextColorForward(const char * colorStart) const { + + if (*(colorStart + 1) == '\\') { + m_textColor = Palette::PrimaryText; + return (*(colorStart + 3) == '%' || *(colorStart + 4) == '%'); + } + + int keySize = 1; + KDColor lastColor = m_textColor; + + switch (*(colorStart+1)) + { + case 'r': + if (*(colorStart+2) == 'l') { + m_textColor = Palette::RedLight; + keySize = 2; + } + else { + m_textColor = Palette::Red; + } + break; + case 'm': + m_textColor = Palette::Magenta; + break; + case 't': + m_textColor = Palette::Turquoise; + break; + case 'p': + if (*(colorStart+2) == 'k') { + m_textColor = Palette::Pink; + keySize = 2; + } + else if (*(colorStart+2) == 'p') { + m_textColor = Palette::Purple; + keySize = 2; + } + break; + case 'b': + if (*(colorStart+2) == 'r') { + m_textColor = Palette::Brown; + keySize = 2; + } + if (*(colorStart+2) == 'l') { + m_textColor = Palette::BlueLight; + keySize = 2; + } + else { + m_textColor = Palette::Blue; + } + break; + case 'o': + m_textColor = Palette::Orange; + break; + case 'g': + if (*(colorStart+2) == 'l') { + m_textColor = Palette::GreenLight; + keySize = 2; + } + else { + m_textColor = Palette::Green; + } + break; + case 'c': + m_textColor = Palette::Cyan; + break; + + default: + return false; + } + + if (*(colorStart + keySize + 1) != '%') { + m_textColor = lastColor; + return false; + } + + return true; +} + +bool WordWrapTextView::updateTextColorBackward(const char * colorStart) const { + + if (*(colorStart++) != '\\') { + return false; + } + + int keySize = 1; + KDColor lastColor = m_textColor; + switch (*(colorStart+1)) + { + case 'r': + if (*(colorStart+2) == 'l') { + m_textColor = Palette::RedLight; + keySize = 2; + } + else { + m_textColor = Palette::Red; + } + break; + case 'm': + m_textColor = Palette::Magenta; + break; + case 't': + m_textColor = Palette::Turquoise; + break; + case 'p': + if (*(colorStart+2) == 'k') { + m_textColor = Palette::Pink; + keySize = 2; + } + else if (*(colorStart+2) == 'p') { + m_textColor = Palette::Purple; + keySize = 2; + } + break; + case 'b': + if (*(colorStart+2) == 'r') { + m_textColor = Palette::Brown; + keySize = 2; + } + if (*(colorStart+2) == 'l') { + m_textColor = Palette::BlueLight; + keySize = 2; + } + else { + m_textColor = Palette::Blue; + } + break; + case 'o': + m_textColor = Palette::Orange; + break; + case 'g': + if (*(colorStart+2) == 'l') { + m_textColor = Palette::GreenLight; + keySize = 2; + } + else { + m_textColor = Palette::Green; + } + break; + case 'c': + m_textColor = Palette::Cyan; + break; + + default: + return false; + } + + if (*(colorStart + keySize + 1) != '%') { + m_textColor = lastColor; + return false; + } + + return true; +} + +} diff --git a/apps/reader/word_wrap_view.h b/apps/reader/word_wrap_view.h index cd17964ae..92526ec88 100644 --- a/apps/reader/word_wrap_view.h +++ b/apps/reader/word_wrap_view.h @@ -7,20 +7,28 @@ namespace Reader { +struct BookSave { + int offset; + KDColor color; +}; + class WordWrapTextView : public PointerTextView { public: - WordWrapTextView() : PointerTextView(GlobalPreferences::sharedGlobalPreferences()->font()) {}; + WordWrapTextView(); void drawRect(KDContext * ctx, KDRect rect) const override; void setText(const char*, int length); void nextPage(); void previousPage(); - int getPageOffset() const; - void setPageOffset(int o); -protected: - int m_pageOffset = 0; - mutable int m_nextPageOffset = 0; - int m_length = 0; + BookSave getBookSave() const; + void setBookSave(BookSave save); +private: + bool updateTextColorForward(const char * colorStart) const; + bool updateTextColorBackward(const char * colorStart) const; static const int k_margin = 10; + int m_pageOffset; + mutable int m_nextPageOffset; + int m_length; + mutable KDColor m_textColor; }; }