From 73f2a7ecaccdac829272e23bb53c388e4b8cd04e Mon Sep 17 00:00:00 2001 From: Laury Date: Fri, 22 Oct 2021 21:23:35 +0200 Subject: [PATCH] [reader] Foundation of latex handling --- apps/reader/Makefile | 1 + apps/reader/tex_parser.cpp | 124 +++++++++++++++++++++++++++++++++ apps/reader/tex_parser.h | 31 +++++++++ apps/reader/utility.cpp | 19 ++++- apps/reader/utility.h | 1 + apps/reader/word_wrap_view.cpp | 74 +++++++++++++------- 6 files changed, 225 insertions(+), 25 deletions(-) create mode 100644 apps/reader/tex_parser.cpp create mode 100644 apps/reader/tex_parser.h diff --git a/apps/reader/Makefile b/apps/reader/Makefile index f786e1510..5466537e7 100644 --- a/apps/reader/Makefile +++ b/apps/reader/Makefile @@ -7,6 +7,7 @@ app_sreader_src = $(addprefix apps/reader/,\ utility.cpp \ read_book_controller \ word_wrap_view.cpp \ + tex_parser.cpp \ ) apps_src += $(app_sreader_src) diff --git a/apps/reader/tex_parser.cpp b/apps/reader/tex_parser.cpp new file mode 100644 index 000000000..026c6493d --- /dev/null +++ b/apps/reader/tex_parser.cpp @@ -0,0 +1,124 @@ +#include "tex_parser.h" +#include + +namespace Reader { + +TexParser::TexParser(const char * text, const char * endOfText) : + m_text(text), + m_endOfText(endOfText), + m_hasError(false) +{ + +} + +Layout TexParser::getLayout() { + HorizontalLayout layout = HorizontalLayout::Builder(); + const char * start = m_text; + + while (m_text < m_endOfText) { + if (*m_text == '\\') { + if (start != m_text) { + layout.addOrMergeChildAtIndex(LayoutHelper::String(start, m_text - start), layout.numberOfChildren(), false); + } + m_text ++; + layout.addOrMergeChildAtIndex(popCommand(), layout.numberOfChildren(), false); + start = m_text; + } + else if (*m_text == " ") { + if (start != m_text) { + layout.addOrMergeChildAtIndex(LayoutHelper::String(start, m_text - start), layout.numberOfChildren(), false); + } + m_text ++; + start = m_text; + } + else if (*m_text == "^") { + if (start != m_text) { + layout.addOrMergeChildAtIndex(LayoutHelper::String(start, m_text - start), layout.numberOfChildren(), false); + } + m_text ++; + layout.addOrMergeChildAtIndex(popCommand(), layout.numberOfChildren(), false); + start = m_text; + } + else { + m_text ++; + } + } + + if (start != m_text) { + layout.addOrMergeChildAtIndex(LayoutHelper::String(start, m_text - start), layout.numberOfChildren(), false); + } + + if (m_hasError) { + return CodePointLayout::Builder(CodePoint(0xFFD)); + } + + return layout; +} + +Layout TexParser::popBlock() { + while (*m_text == ' ') { + m_text ++; + } + + if (*m_text == '{') { + m_text ++; + return popText('}'); + } + + if (*m_text == '\\') { + m_text ++; + return popCommand(); + } + + if (m_text >= m_endOfText) { + m_hasError = true; + } + + UTF8Decoder decoder(m_text); + m_text ++; + return CodePointLayout::Builder(decoder.nextCodePoint()); +} + +Layout TexParser::popText(char stop) { + if (*m_text == '\\') { + m_text ++; + return popCommand(); + } + + HorizontalLayout layout = HorizontalLayout::Builder(); + const char * start = m_text; + + while (m_text < m_endOfText) { + if (*m_text == '\\') { + layout.addOrMergeChildAtIndex(LayoutHelper::String(start, m_text - start), layout.numberOfChildren(), false); + m_text ++; + layout.addOrMergeChildAtIndex(popCommand(), layout.numberOfChildren(), false); + start = m_text; + } + + m_text ++; + } + + if (start != m_text) { + layout.addOrMergeChildAtIndex(LayoutHelper::String(start, m_text - start), layout.numberOfChildren(), false); + } + return layout; +} + +Layout TexParser::popCommand() { + if (strncmp(k_fracCommand, m_text, strlen(k_fracCommand)) == 0) { + m_text += strlen(k_fracCommand); + if (*m_text == ' ' || *m_text == '{') { + return popFracCommand(); + } + } + return popFracCommand(); +} + +Layout TexParser::popFracCommand() { + Layout firstBlock = popBlock(); + Layout secondBlock = popBlock(); + return FractionLayout::Builder(firstBlock, secondBlock); +} + +} diff --git a/apps/reader/tex_parser.h b/apps/reader/tex_parser.h new file mode 100644 index 000000000..6119a1403 --- /dev/null +++ b/apps/reader/tex_parser.h @@ -0,0 +1,31 @@ +#ifndef __TEX_PARSER_H__ +#define __TEX_PARSER_H__ + +#include +#include +#include + +using namespace Poincare; + +namespace Reader +{ +/// @brief Class used in the WordWrapTextView class to parse a Tex expression +class TexParser { +public: + TexParser(const char * text, const char * endOfText); + Layout getLayout(); +private: + Layout popBlock(); + Layout popText(char stop); + Layout popCommand(); + Layout popFracCommand(); + const char * m_text; + const char * m_endOfText; + bool m_hasError; + + static constexpr char const * k_fracCommand = "frac"; +}; + +} + +#endif diff --git a/apps/reader/utility.cpp b/apps/reader/utility.cpp index d941039cd..24605d14c 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 != '%') { + while (codePoint != '\n' && codePoint != ' ' && codePoint != '%' && codePoint != '$') { result = decoder.stringPosition(); if (result >= end) { break; @@ -127,4 +127,21 @@ const char * EndOfPrintableWord(const char * word, const char * end) { return result; } +const char * StartOfPrintableWord(const char * word, const char * start) { + if (word == start) { + return word; + } + UTF8Decoder decoder(word); + CodePoint codePoint = decoder.previousCodePoint(); + const char * result = word; + while (codePoint != '\n' && codePoint != ' ' && codePoint != '%' && codePoint != '$') { + result = decoder.stringPosition(); + if (result >= start) { + break; + } + codePoint = decoder.previousCodePoint(); + } + return result; +} + } \ No newline at end of file diff --git a/apps/reader/utility.h b/apps/reader/utility.h index 55a6e8c33..61aabbf2c 100644 --- a/apps/reader/utility.h +++ b/apps/reader/utility.h @@ -12,6 +12,7 @@ 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); +const char * StartOfPrintableWord(const char * word, const char * start); } #endif \ No newline at end of file diff --git a/apps/reader/word_wrap_view.cpp b/apps/reader/word_wrap_view.cpp index 49ea133ab..4eaa5c783 100644 --- a/apps/reader/word_wrap_view.cpp +++ b/apps/reader/word_wrap_view.cpp @@ -1,6 +1,7 @@ #include "word_wrap_view.h" #include "utility.h" +#include "tex_parser.h" #include #include "../shared/poincare_helpers.h" #include @@ -38,16 +39,17 @@ void WordWrapTextView::previousPage() { 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 - 1; - const char * startOfWord = UTF8Helper::BeginningOfWord(text(), endOfWord); + const char * startOfWord = StartOfPrintableWord(endOfWord, text()); 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); + startOfWord = StartOfPrintableWord(endOfWord, text()); + endOfWord = EndOfPrintableWord(startOfWord, endOfFile); if (*startOfWord == '%') { if (updateTextColorBackward(startOfWord)) { @@ -55,20 +57,28 @@ void WordWrapTextView::previousPage() { 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(); + + if (*endOfWord == '$') { + startOfWord = endOfWord - 1; + while (*startOfWord != '$') { + if (startOfWord < text()) { + break; // File isn't rightly formated + } + startOfWord --; } - Poincare::Layout layout = Shared::PoincareHelpers::CreateLayout(expr); - textSize = layout.layoutSize(); - + startOfWord --; + + TexParser parser = TexParser(startOfWord + 1, endOfWord - 2); + Poincare::Layout layout = parser.getLayout(); + textSize = layout.layoutSize(); } else { - textSize = m_font->stringSizeUntil(startOfWord, endOfWord); + if (*startOfWord == '\\' || *(startOfWord + 1) == '$') { + textSize = m_font->stringSizeUntil(startOfWord + 1, endOfWord); + } + else { + textSize = m_font->stringSizeUntil(startOfWord, endOfWord); + } } KDPoint textStartPosition = KDPoint(textEndPosition.x()-textSize.width(), textEndPosition.y()); @@ -109,7 +119,7 @@ void WordWrapTextView::previousPage() { m_pageOffset = 0; } else { - m_pageOffset = UTF8Helper::EndOfWord(startOfWord) - text() + 1; + m_pageOffset = EndOfPrintableWord(startOfWord, endOfFile) - text() + 1; } markRectAsDirty(bounds()); } @@ -119,7 +129,12 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { const char * endOfFile = text() + m_length; const char * startOfWord = text() + m_pageOffset; - const char * endOfWord = EndOfPrintableWord(startOfWord, endOfFile); + 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; @@ -152,18 +167,26 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { 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(); + + if (*startOfWord == '$') { // Look for expression + endOfWord = startOfWord + 1; + while (*endOfWord != '$') { + if (endOfWord > endOfFile) { + break; // If we are here, it's bad... + } + endOfWord ++; } - layout = Shared::PoincareHelpers::CreateLayout(expr); + endOfWord ++; + + TexParser parser = TexParser(startOfWord + 1, endOfWord - 1); + layout = parser.getLayout(); textSize = layout.layoutSize(); toDraw = ToDraw::Expression; } else { + if (*startOfWord == '\\' || *(startOfWord + 1) == '$') { + startOfWord ++; + } textSize = m_font->stringSizeUntil(startOfWord, endOfWord); stringNCopy(word, wordMaxLength, startOfWord, endOfWord-startOfWord); toDraw = ToDraw::Text; @@ -222,7 +245,10 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { } textPosition = nextTextPosition; - endOfWord = EndOfPrintableWord(startOfWord+1, endOfFile); + + if (*startOfWord != '$') { + endOfWord = EndOfPrintableWord(startOfWord+1, endOfFile); + } // Else we don't need to update endOfWord } m_nextPageOffset = startOfWord - text();