[reader] Rich text support (look at README.md)

This commit is contained in:
Laury
2021-10-15 14:43:35 +02:00
parent d4f0c7d3e8
commit 83ce9d5e86
7 changed files with 340 additions and 35 deletions

27
apps/reader/README.md Normal file
View File

@@ -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|

View File

@@ -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<const int*>(r.value().buffer));
m_readerView.setPageOffset(pageOffset);
BookSave save = *(static_cast<const BookSave*>(r.value().buffer));
m_readerView.setBookSave(save);
}
else {
m_readerView.setPageOffset(0);
m_readerView.setBookSave({
0,
Palette::PrimaryText
});
}
}

View File

@@ -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:

View File

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

View File

@@ -2,6 +2,8 @@
#define __UTILITY_H__
#include <apps/external/archive.h>
#include <ion/unicode/code_point.h>
#include <ion/unicode/utf8_decoder.h>
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

View File

@@ -1,10 +1,22 @@
#include "word_wrap_view.h"
#include "utility.h"
#include <poincare/expression.h>
#include "../shared/poincare_helpers.h"
#include <poincare/undefined.h>
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;
}
}
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;
}
}

View File

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