move parser into add-in (loses course/questions distinction)
This commit is contained in:
parent
25827479ac
commit
318522d791
|
@ -10,7 +10,9 @@ find_package(Gint 2.9 REQUIRED)
|
|||
|
||||
set(SOURCES
|
||||
src/brk.c
|
||||
src/document.c
|
||||
src/main.c
|
||||
src/parser.c
|
||||
src/reader.c)
|
||||
set(ASSETS
|
||||
assets/uf8x9
|
||||
|
@ -29,7 +31,6 @@ set(ASSETS
|
|||
math/13_arithmetique.tex)
|
||||
|
||||
fxconv_declare_assets(${ASSETS} WITH_METADATA)
|
||||
fxconv_declare_converters(converters.py)
|
||||
|
||||
add_executable(addin ${SOURCES} ${ASSETS})
|
||||
target_compile_options(addin PRIVATE -Wall -Wextra -Os)
|
||||
|
|
164
converters.py
164
converters.py
|
@ -1,164 +0,0 @@
|
|||
import fxconv
|
||||
import re
|
||||
import enum
|
||||
|
||||
def convert(input, output, params, target):
|
||||
recognized = True
|
||||
|
||||
if params["custom-type"] == "chapter":
|
||||
o = convert_chapter(input, output, params)
|
||||
else:
|
||||
recognized = False
|
||||
|
||||
if recognized:
|
||||
fxconv.elf(o, output, "_" + params["name"], **target)
|
||||
return 0
|
||||
|
||||
return 1
|
||||
|
||||
# Lex document files line-by-line
|
||||
T = enum.Enum("T", [
|
||||
"CHAPTER", "METADATA", "COURSE", "QUESTION",
|
||||
"SLIDE_SEP", "LINE_TEXT", "LINE_MATH",
|
||||
"END"
|
||||
])
|
||||
LINE_TYPE_TEXT = 0
|
||||
LINE_TYPE_DISPLAY_MATH = 1
|
||||
|
||||
def lex(lines):
|
||||
re_keyword = re.compile(r'%\s*([a-z]+):\s*(.*)')
|
||||
re_slide_sep = re.compile(r'---')
|
||||
re_line_math = re.compile(r'\\\[\s*(|.*\S)\s*\\\]')
|
||||
|
||||
for l in lines:
|
||||
l = l.strip()
|
||||
if l == "":
|
||||
continue
|
||||
|
||||
m = re_keyword.fullmatch(l)
|
||||
if m:
|
||||
value = m[2].strip()
|
||||
if len(value) >= 2 and value[0] == '"' and value[-1] == '"':
|
||||
value = value[1:-1]
|
||||
if m[1] == 'chapitre':
|
||||
yield (T.CHAPTER, value)
|
||||
elif m[1] == 'bg':
|
||||
yield (T.METADATA, ("bg", value))
|
||||
elif m[1] == 'cours':
|
||||
yield (T.COURSE, value)
|
||||
elif m[1] == 'question':
|
||||
yield (T.QUESTION, value)
|
||||
else:
|
||||
raise fxconv.FxconvError(f"unknown %-key '{m[1]}' ({m[2]})")
|
||||
continue
|
||||
|
||||
m = re_slide_sep.fullmatch(l)
|
||||
if m:
|
||||
yield (T.SLIDE_SEP, None)
|
||||
continue
|
||||
|
||||
m = re_line_math.fullmatch(l)
|
||||
if m:
|
||||
yield (T.LINE_MATH, m[1])
|
||||
continue
|
||||
|
||||
yield (T.LINE_TEXT, l)
|
||||
|
||||
while True:
|
||||
yield (T.END, None)
|
||||
|
||||
class Parser:
|
||||
def __init__(self, lexer):
|
||||
self.lexer = lexer
|
||||
self.la = next(self.lexer)
|
||||
print(self.la)
|
||||
|
||||
def expect(self, type):
|
||||
if self.la[0] != type:
|
||||
raise fxconv.FxconvError(
|
||||
f"parse error, expected {type}, got {self.la[0]}")
|
||||
data = self.la[1]
|
||||
self.la = next(self.lexer)
|
||||
print(self.la)
|
||||
return data
|
||||
|
||||
def _chapter(self):
|
||||
chapter_title = self.expect(T.CHAPTER)
|
||||
bg = 0
|
||||
subjects = 0
|
||||
questions = 0
|
||||
o_subjects = fxconv.Structure()
|
||||
o_questions = fxconv.Structure()
|
||||
|
||||
while self.la[0] == T.METADATA:
|
||||
key, value = self.expect(T.METADATA)
|
||||
if key == "bg" and value.startswith("0x"):
|
||||
bg = int(value, 0)
|
||||
else:
|
||||
raise fxconv.FxconvError(f"invalid metadata '{key}: {value}'")
|
||||
|
||||
while self.la[0] == T.COURSE:
|
||||
title = self.expect(T.COURSE)
|
||||
subjects += 1
|
||||
o_subjects += self._slides(title)
|
||||
while self.la[0] == T.QUESTION:
|
||||
title = self.expect(T.QUESTION)
|
||||
questions += 1
|
||||
o_questions += self._slides(title)
|
||||
self.expect(T.END)
|
||||
|
||||
o = fxconv.Structure()
|
||||
o += fxconv.string(chapter_title)
|
||||
o += fxconv.u32(bg)
|
||||
o += fxconv.u32(subjects)
|
||||
o += fxconv.ptr(o_subjects)
|
||||
o += fxconv.u32(questions)
|
||||
o += fxconv.ptr(o_questions)
|
||||
return o
|
||||
|
||||
def _slides(self, title):
|
||||
slides = 0
|
||||
o_slides = fxconv.Structure()
|
||||
|
||||
while True:
|
||||
slides += 1
|
||||
o_slides += self._slide()
|
||||
print("got a slide")
|
||||
print(o_slides.inner)
|
||||
if self.la[0] != T.SLIDE_SEP:
|
||||
break
|
||||
self.expect(T.SLIDE_SEP)
|
||||
|
||||
o = fxconv.Structure()
|
||||
o += fxconv.string(title)
|
||||
o += fxconv.u32(slides)
|
||||
o += fxconv.ptr(o_slides)
|
||||
return o
|
||||
|
||||
def _slide(self):
|
||||
lines = 0
|
||||
o_lines = fxconv.Structure()
|
||||
|
||||
while self.la[0] in [T.LINE_TEXT, T.LINE_MATH]:
|
||||
type = self.la[0]
|
||||
text = self.expect(type)
|
||||
lines += 1
|
||||
|
||||
o_lines += fxconv.u32({
|
||||
T.LINE_TEXT: LINE_TYPE_TEXT,
|
||||
T.LINE_MATH: LINE_TYPE_DISPLAY_MATH,
|
||||
}[type])
|
||||
o_lines += fxconv.string(text)
|
||||
|
||||
o = fxconv.Structure()
|
||||
o += fxconv.u32(lines)
|
||||
o += fxconv.ptr(o_lines)
|
||||
return o
|
||||
|
||||
def convert_chapter(input, output, params):
|
||||
with open(input, "r") as fp:
|
||||
text = fp.read()
|
||||
|
||||
l = lex(text.splitlines())
|
||||
p = Parser(l)
|
||||
return p._chapter()
|
|
@ -11,8 +11,6 @@ Elle est définie par son premier terme $u_p$ et le réel r qui est appelé "rai
|
|||
|
||||
\[ u_n = u_p + (n - p) × r \]
|
||||
|
||||
---
|
||||
|
||||
La somme $(S_n)$ de $(u_n)$ est :
|
||||
|
||||
\[ S_n = (n-p+1) \frac{u_n + u_p}{2} \]
|
||||
|
@ -27,8 +25,6 @@ q s'appelle la raison. Quand le premier terme est $v_p$, la suite est
|
|||
|
||||
\[ v_n = v_p × q^{n-p} \]
|
||||
|
||||
---
|
||||
|
||||
La somme $(S_n)$ de $(v_n)$ est :
|
||||
|
||||
\[ S_n = v_p × \frac{1 - q^{n-p+1}}{1-q} \]
|
||||
|
@ -47,8 +43,6 @@ Quand $u_n > 0$, on peut aussi étudier $u_{n+1} / u_n$.
|
|||
- $(u_n)$ est croissante si $u_{n+1} / u_n ≥ 1$
|
||||
- $(u_n)$ est décroissante si $u_{n+1} / u_n ≤ 1$
|
||||
|
||||
---
|
||||
|
||||
Si $(u_n)$ est de la forme $u_n = f(n)$, il faut étudier les variations de f.
|
||||
|
||||
Si $u_{n+1} = f(u_n)$, il faut faire un raisonnement par récurrence.
|
||||
|
@ -85,8 +79,6 @@ Lorsque $(u_n)$ est divergente :
|
|||
- Si elle est minorée, elle est convergente
|
||||
- Sinon, elle tend vers -∞.
|
||||
|
||||
---
|
||||
|
||||
Si $(u_n)$ est croissante et tend vers l, alors elle est majorée par l.
|
||||
|
||||
Si $(u_n)$ est décroissante et tend vers l, alors est elle minorée par l.
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
*.tex:
|
||||
name_regex: (.*)\.tex math_\1
|
||||
custom-type: chapter
|
||||
name_regex: (.*)\.tex file_\1_tex
|
||||
type: binary
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "document.h"
|
||||
|
||||
static struct element *mkelement(void)
|
||||
{
|
||||
return calloc(1, sizeof (struct element));
|
||||
}
|
||||
|
||||
static struct element *mkelement_with_text(char const *str, int len)
|
||||
{
|
||||
struct element *e = mkelement();
|
||||
if(!e)
|
||||
return NULL;
|
||||
|
||||
e->str = (len >= 0) ? strndup(str, len) : strdup(str);
|
||||
if(!e->str) {
|
||||
free(e);
|
||||
return NULL;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
struct element *element_new_inline_text(char const *str, int len)
|
||||
{
|
||||
struct element *e = mkelement_with_text(str, len);
|
||||
if(e)
|
||||
e->type = ELEMENT_INLINE_TEXT;
|
||||
return e;
|
||||
}
|
||||
|
||||
struct element *element_new_inline_math(char const *str, int len)
|
||||
{
|
||||
struct element *e = mkelement_with_text(str, len);
|
||||
if(e)
|
||||
e->type = ELEMENT_INLINE_MATH;
|
||||
return e;
|
||||
}
|
||||
|
||||
struct element *element_new_new_paragraph(void)
|
||||
{
|
||||
struct element *e = mkelement();
|
||||
if(e)
|
||||
e->type = ELEMENT_NEW_PARAGRAPH;
|
||||
return e;
|
||||
}
|
||||
|
||||
struct element *element_new_block_math(char const *str, int len)
|
||||
{
|
||||
struct element *e = mkelement_with_text(str, len);
|
||||
if(e)
|
||||
e->type = ELEMENT_BLOCK_MATH;
|
||||
return e;
|
||||
}
|
||||
|
||||
void element_free(struct element *e)
|
||||
{
|
||||
free(e->str);
|
||||
free(e);
|
||||
}
|
||||
|
||||
struct page *page_new(void)
|
||||
{
|
||||
return calloc(1, sizeof(struct page));
|
||||
}
|
||||
|
||||
void page_set_title(struct page *page, char const *title, int len)
|
||||
{
|
||||
free(page->title);
|
||||
page->title = NULL;
|
||||
if(title)
|
||||
page->title = (len >= 0) ? strndup(title, len) : strdup(title);
|
||||
}
|
||||
|
||||
void page_set_category(struct page *page, char const *ctgy, int len)
|
||||
{
|
||||
free(page->category);
|
||||
page->category = NULL;
|
||||
if(ctgy)
|
||||
page->category = (len >= 0) ? strndup(ctgy, len) : strdup(ctgy);
|
||||
}
|
||||
|
||||
void page_add_element(struct page *page, struct element *element)
|
||||
{
|
||||
int new_count = page->element_count + 1;
|
||||
struct element **new_buf =
|
||||
realloc(page->elements, new_count * sizeof *new_buf);
|
||||
if(!new_buf)
|
||||
return;
|
||||
|
||||
new_buf[new_count - 1] = element;
|
||||
page->elements = new_buf;
|
||||
page->element_count = new_count;
|
||||
}
|
||||
|
||||
void page_free(struct page *page)
|
||||
{
|
||||
if(!page)
|
||||
return;
|
||||
free(page->title);
|
||||
free(page->category);
|
||||
for(int i = 0; i < page->element_count; i++)
|
||||
element_free(page->elements[i]);
|
||||
free(page->elements);
|
||||
free(page);
|
||||
}
|
||||
|
||||
struct document *document_new(void)
|
||||
{
|
||||
return calloc(1, sizeof(struct document));
|
||||
}
|
||||
|
||||
void document_set_title(struct document *doc, char const *title, int len)
|
||||
{
|
||||
free(doc->title);
|
||||
doc->title = NULL;
|
||||
if(title)
|
||||
doc->title = (len >= 0) ? strndup(title, len) : strdup(title);
|
||||
}
|
||||
|
||||
void document_set_symbol(struct document *doc, char const *symbol, int len)
|
||||
{
|
||||
free(doc->symbol);
|
||||
doc->symbol = NULL;
|
||||
if(symbol)
|
||||
doc->symbol = (len >= 0) ? strndup(symbol, len) : strdup(symbol);
|
||||
}
|
||||
|
||||
void document_set_bg(struct document *doc, int bg)
|
||||
{
|
||||
doc->bgcolor = bg;
|
||||
}
|
||||
|
||||
void document_add_page(struct document *doc, struct page *page)
|
||||
{
|
||||
int new_count = doc->page_count + 1;
|
||||
struct page **new_buf = realloc(doc->pages, new_count * sizeof *new_buf);
|
||||
if(!new_buf)
|
||||
return;
|
||||
|
||||
new_buf[new_count - 1] = page;
|
||||
doc->pages = new_buf;
|
||||
doc->page_count = new_count;
|
||||
}
|
||||
|
||||
void document_free(struct document *doc)
|
||||
{
|
||||
if(!doc)
|
||||
return;
|
||||
free(doc->title);
|
||||
free(doc->symbol);
|
||||
for(int i = 0; i < doc->page_count; i++)
|
||||
page_free(doc->pages[i]);
|
||||
free(doc->pages);
|
||||
free(doc);
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
#pragma once
|
||||
#include <stddef.h>
|
||||
|
||||
/* High-level document for the purpose of this application; it is a list of
|
||||
pages (single-page documents flowing top to bottom) with categories. */
|
||||
struct document {
|
||||
char *title;
|
||||
char *symbol;
|
||||
int bgcolor;
|
||||
|
||||
/* All pages are owned by the document. */
|
||||
int page_count;
|
||||
struct page **pages;
|
||||
};
|
||||
|
||||
|
||||
/* Linear, mostly unstructured sequence of elements. Some elements are inline,
|
||||
some are blocks, some are inline blocks, resulting in varied layouts. This
|
||||
intentionally has no nested structure because I don't have time for
|
||||
something complicated right now. Morally, it could be more complicated. */
|
||||
struct page {
|
||||
char *title;
|
||||
char *category;
|
||||
|
||||
/* Element vector owned by the page. */
|
||||
int element_count;
|
||||
struct element **elements;
|
||||
};
|
||||
|
||||
/* page element, a single inline or block object in the page. */
|
||||
enum {
|
||||
ELEMENT_INLINE_TEXT, /* Inline text */
|
||||
ELEMENT_INLINE_MATH, /* Inline math */
|
||||
ELEMENT_NEW_PARAGRAPH, /* Paragraph delimited */
|
||||
ELEMENT_BLOCK_MATH, /* Display math */
|
||||
};
|
||||
struct element {
|
||||
int type;
|
||||
/* Optional NUL-terminated string, owned by the element. */
|
||||
char *str;
|
||||
};
|
||||
|
||||
//======= API for manipulating documents =======//
|
||||
|
||||
/* Make new elements */
|
||||
struct element *element_new_inline_text(char const *str, int len);
|
||||
struct element *element_new_inline_math(char const *str, int len);
|
||||
struct element *element_new_new_paragraph(void);
|
||||
struct element *element_new_block_math(char const *str, int len);
|
||||
|
||||
/* Free an element */
|
||||
void element_free(struct element *element);
|
||||
|
||||
/* Make an empty page, set its metadata, and free it (with its elements). */
|
||||
struct page *page_new(void);
|
||||
void page_set_title(struct page *page, char const *title, int len);
|
||||
void page_set_category(struct page *page, char const *category, int len);
|
||||
void page_free(struct page *page);
|
||||
|
||||
/* Add elements to a page. */
|
||||
void page_add_element(struct page *page, struct element *element);
|
||||
|
||||
/* Make an empty document, set its metadata, free it (pages and their elements
|
||||
are freed as well). */
|
||||
struct document *document_new(void);
|
||||
void document_set_title(struct document *doc, char const *title, int len);
|
||||
void document_set_symbol(struct document *doc, char const *symbol, int len);
|
||||
void document_set_bg(struct document *doc, int bg);
|
||||
void document_free(struct document *doc);
|
||||
|
||||
/* Add pages to the document. */
|
||||
void document_add_page(struct document *doc, struct page *page);
|
||||
|
||||
/* Parser a document from a text file. */
|
||||
struct document *document_parse(char const *str, int len);
|
192
src/main.c
192
src/main.c
|
@ -1,59 +1,61 @@
|
|||
#include <gint/display.h>
|
||||
#include <gint/keyboard.h>
|
||||
#include <TeX/TeX.h>
|
||||
#include "mathsts.h"
|
||||
#include "document.h"
|
||||
#include "reader.h"
|
||||
|
||||
#define ENUM_CHAPTERS(X) \
|
||||
X(math_01_suites) \
|
||||
X(math_02_complexes) \
|
||||
X(math_03_limites) \
|
||||
X(math_04_derivation) \
|
||||
X(math_05_exponentielle) \
|
||||
X(math_06_logarithme) \
|
||||
X(math_07_trigo) \
|
||||
X(math_08_integration) \
|
||||
X(math_09_geometrie) \
|
||||
X(math_10_probabilites) \
|
||||
X(math_11_loisprobas) \
|
||||
X(math_12_stats) \
|
||||
X(math_13_arithmetique) \
|
||||
#define ENUM_FILES(X) \
|
||||
X(file_01_suites_tex) \
|
||||
X(file_02_complexes_tex) \
|
||||
X(file_03_limites_tex) \
|
||||
X(file_04_derivation_tex) \
|
||||
X(file_05_exponentielle_tex) \
|
||||
X(file_06_logarithme_tex) \
|
||||
X(file_07_trigo_tex) \
|
||||
X(file_08_integration_tex) \
|
||||
X(file_09_geometrie_tex) \
|
||||
X(file_10_probabilites_tex) \
|
||||
X(file_11_loisprobas_tex) \
|
||||
X(file_12_stats_tex) \
|
||||
X(file_13_arithmetique_tex) \
|
||||
|
||||
#define EXTERN_DECLARE(NAME) extern struct chapter const NAME;
|
||||
ENUM_CHAPTERS(EXTERN_DECLARE)
|
||||
#define EXTERN_DECLARE(NAME) \
|
||||
extern char const NAME[]; \
|
||||
extern int const NAME ## _size;
|
||||
ENUM_FILES(EXTERN_DECLARE)
|
||||
|
||||
#define INITIALIZE(NAME) &NAME,
|
||||
static struct chapter const *chapters[] = { ENUM_CHAPTERS(INITIALIZE) };
|
||||
struct source {
|
||||
char const *str;
|
||||
int len;
|
||||
};
|
||||
|
||||
#define INITIALIZE(NAME) { NAME, (int)&NAME ## _size },
|
||||
static struct source const files[] = { ENUM_FILES(INITIALIZE) };
|
||||
|
||||
#define COUNT(NAME) +1
|
||||
static int file_count = 0 ENUM_FILES(COUNT);
|
||||
|
||||
// TODO: Incorporate chapter symbols into TeX files
|
||||
static char const *chapter_symbols[13] = {
|
||||
"Un", "Z", "lim", "f'", "e ", "ln", "cos", "∫dx",
|
||||
"u", "P(A)", "λ", "In", "[k]",
|
||||
"Un", "Z", "lim", "f'", "e^x", "ln", "cos", "∫dx",
|
||||
"\\vec{u}", "P(A)", "λ", "In", "[k]",
|
||||
};
|
||||
|
||||
static void render_chapter_tile(int chapter, int where, bool selected)
|
||||
static void render_document_tile(struct document const *doc, int symbolid,
|
||||
int where, bool selected)
|
||||
{
|
||||
if((unsigned)chapter >= 13)
|
||||
return;
|
||||
|
||||
int y = 87;
|
||||
int middle = DWIDTH / 2 + 67 * where + 6 * ((where > 0) - (where < 0));
|
||||
int r = (where == 0) ? 26 : 20;
|
||||
|
||||
drect(middle-r, y-r, middle+r, y+r, chapters[chapter]->bgcolor);
|
||||
if(selected) {
|
||||
drect_border(middle-r-2, y-r-2, middle+r+2, y+r+2, C_NONE, 1,
|
||||
chapters[chapter]->bgcolor);
|
||||
}
|
||||
dtext_opt(middle, y, C_WHITE, C_NONE, DTEXT_CENTER, DTEXT_MIDDLE,
|
||||
chapter_symbols[chapter], -1);
|
||||
int color = doc ? doc->bgcolor : C_RGB(16, 16, 16);
|
||||
|
||||
/* Special decorations */
|
||||
if(chapter == 4)
|
||||
dtext_opt(middle-1, y-6, C_WHITE, C_NONE, DTEXT_CENTER, DTEXT_MIDDLE,
|
||||
" x", -1);
|
||||
if(chapter == 8)
|
||||
dtext_opt(middle+1, y-6, C_WHITE, C_NONE, DTEXT_CENTER, DTEXT_MIDDLE,
|
||||
"→", -1);
|
||||
drect(middle-r, y-r, middle+r, y+r, color);
|
||||
if(selected)
|
||||
drect_border(middle-r-2, y-r-2, middle+r+2, y+r+2, C_NONE, 1, color);
|
||||
if(symbolid >= 0)
|
||||
dtext_opt(middle, y, C_WHITE, C_NONE, DTEXT_CENTER, DTEXT_MIDDLE,
|
||||
chapter_symbols[symbolid], -1);
|
||||
}
|
||||
|
||||
void render_buttons(int x0, int y, int count, int selected, int color,
|
||||
|
@ -84,7 +86,8 @@ static void textext(char const *str, int x, int y, int color)
|
|||
dtext(x, y, color, str);
|
||||
}
|
||||
|
||||
static int move_details_cursor(struct chapter const *ch, int c_details,
|
||||
#if 0
|
||||
static int move_details_cursor(struct chapter const *ch, int c_page,
|
||||
int new_details)
|
||||
{
|
||||
int row = new_details & 1;
|
||||
|
@ -92,10 +95,11 @@ static int move_details_cursor(struct chapter const *ch, int c_details,
|
|||
int max = row ? ch->questions_size : ch->subjects_size;
|
||||
|
||||
if(max == 0)
|
||||
return c_details;
|
||||
return c_page;
|
||||
col = (col < 0) ? 0 : (col >= max) ? max - 1 : col;
|
||||
return 2 * col + row;
|
||||
}
|
||||
#endif
|
||||
|
||||
int main(void)
|
||||
{
|
||||
|
@ -107,14 +111,22 @@ int main(void)
|
|||
TeX_intf_size(texsize);
|
||||
TeX_intf_text(textext);
|
||||
|
||||
// TODO: Also load files from storage memory
|
||||
struct document *documents[13] = { 0 };
|
||||
int document_count = 13;
|
||||
|
||||
for(int i = 0; i < file_count; i++) {
|
||||
documents[i] = document_parse(files[i].str, files[i].len);
|
||||
}
|
||||
|
||||
/* Cursor for selecting stuff in the menu */
|
||||
enum { C_CHAPTER, C_DETAILS } c_mode = C_CHAPTER;
|
||||
int c_chapter = 0;
|
||||
int c_details = 0;
|
||||
enum { C_DOCUMENT, C_PAGE } c_mode = C_DOCUMENT;
|
||||
int c_document = 0;
|
||||
int c_page = 0;
|
||||
|
||||
/* Main menu loop */
|
||||
while(1) {
|
||||
struct chapter const *ch = chapters[c_chapter];
|
||||
struct document const *doc = documents[c_document];
|
||||
|
||||
dclear(C_WHITE);
|
||||
drect(4, 4, DWIDTH-1-4, 44, C_BLACK);
|
||||
|
@ -124,75 +136,97 @@ int main(void)
|
|||
|
||||
/* Tiles */
|
||||
for(int d = -2; d <= +2; d++) {
|
||||
render_chapter_tile(c_chapter + d, d, !d && c_mode == C_CHAPTER);
|
||||
if(c_document + d < 0 || c_document + d >= document_count)
|
||||
continue;
|
||||
render_document_tile(documents[c_document + d],
|
||||
(c_document + d <= 13) ? c_document + d : -1,
|
||||
d, !d && c_mode == C_DOCUMENT);
|
||||
}
|
||||
if(c_chapter > 2) {
|
||||
if(c_document > 2) {
|
||||
dtext_opt(20, 87, C_BLACK, C_NONE, DTEXT_CENTER, DTEXT_MIDDLE,
|
||||
"<", -1);
|
||||
}
|
||||
if(c_chapter < 10) {
|
||||
if(c_document < document_count - 3) {
|
||||
dtext_opt(DWIDTH-20, 87, C_BLACK, C_NONE, DTEXT_CENTER,
|
||||
DTEXT_MIDDLE, ">", -1);
|
||||
}
|
||||
|
||||
/* Chapter preview */
|
||||
dtext_opt(DWIDTH/2, 128, C_BLACK, C_NONE, DTEXT_CENTER, DTEXT_MIDDLE,
|
||||
chapters[c_chapter]->title, -1);
|
||||
if(doc) {
|
||||
dtext_opt(DWIDTH/2, 128, C_BLACK, C_NONE, DTEXT_CENTER,
|
||||
DTEXT_MIDDLE, doc->title, -1);
|
||||
}
|
||||
|
||||
#if 0
|
||||
dtext_opt(100, 160, C_BLACK, C_NONE, DTEXT_RIGHT, DTEXT_MIDDLE,
|
||||
"Cours:", -1);
|
||||
render_buttons(110, 160, ch->subjects_size,
|
||||
(c_mode == C_DETAILS && !(c_details & 1)) ? c_details / 2 : -1,
|
||||
(c_mode == C_PAGE && !(c_page & 1)) ? c_page / 2 : -1,
|
||||
C_BLACK, 14);
|
||||
dtext_opt(100, 176, C_BLACK, C_NONE, DTEXT_RIGHT, DTEXT_MIDDLE,
|
||||
"Questions:", -1);
|
||||
render_buttons(110, 176, ch->questions_size,
|
||||
(c_mode == C_DETAILS && (c_details & 1)) ? c_details / 2 : -1,
|
||||
(c_mode == C_PAGE && (c_page & 1)) ? c_page / 2 : -1,
|
||||
C_BLACK, 14);
|
||||
#endif
|
||||
dtext_opt(100, 160, C_BLACK, C_NONE, DTEXT_RIGHT, DTEXT_MIDDLE,
|
||||
"Pages:", -1);
|
||||
render_buttons(110, 160, doc ? doc->page_count : 0,
|
||||
(c_mode == C_PAGE) ? c_page : -1, C_BLACK, 14);
|
||||
|
||||
if(c_mode == C_DETAILS) {
|
||||
char const *title = (c_details & 1)
|
||||
? ch->questions[c_details / 2].title
|
||||
: ch->subjects[c_details / 2].title;
|
||||
if(c_mode == C_PAGE && doc && doc->pages[c_page]->title) {
|
||||
dtext_opt(26, 200, C_BLACK, C_NONE, DTEXT_LEFT, DTEXT_MIDDLE,
|
||||
title, -1);
|
||||
doc->pages[c_page]->title, -1);
|
||||
}
|
||||
|
||||
dupdate();
|
||||
int key = getkey_opt(GETKEY_DEFAULT & ~GETKEY_MOD_SHIFT, NULL).key;
|
||||
|
||||
if(key == KEY_LEFT && c_mode == C_CHAPTER && c_chapter-1 >= 0) {
|
||||
c_chapter--;
|
||||
c_details = 0;
|
||||
if(key == KEY_LEFT && c_mode == C_DOCUMENT && c_document-1 >= 0) {
|
||||
c_document--;
|
||||
c_page = 0;
|
||||
}
|
||||
if(key == KEY_RIGHT && c_mode == C_CHAPTER && c_chapter+1 < 13) {
|
||||
c_chapter++;
|
||||
c_details = 0;
|
||||
if(key == KEY_RIGHT && c_mode == C_DOCUMENT
|
||||
&& c_document+1 < document_count) {
|
||||
c_document++;
|
||||
c_page = 0;
|
||||
}
|
||||
if(key == KEY_LEFT && c_mode == C_DETAILS)
|
||||
c_details = move_details_cursor(ch, c_details, c_details - 2);
|
||||
if(key == KEY_RIGHT && c_mode == C_DETAILS)
|
||||
c_details = move_details_cursor(ch, c_details, c_details + 2);
|
||||
if(key == KEY_UP && c_mode == C_DETAILS)
|
||||
c_details = move_details_cursor(ch, c_details, c_details & -2);
|
||||
if(key == KEY_DOWN && c_mode == C_DETAILS)
|
||||
c_details = move_details_cursor(ch, c_details, c_details | 1);
|
||||
#if 0
|
||||
if(key == KEY_LEFT && c_mode == C_PAGE)
|
||||
c_page = move_details_cursor(ch, c_page, c_page - 2);
|
||||
if(key == KEY_RIGHT && c_mode == C_PAGE)
|
||||
c_page = move_details_cursor(ch, c_page, c_page + 2);
|
||||
if(key == KEY_UP && c_mode == C_PAGE)
|
||||
c_page = move_details_cursor(ch, c_page, c_page & -2);
|
||||
if(key == KEY_DOWN && c_mode == C_PAGE)
|
||||
c_page = move_details_cursor(ch, c_page, c_page | 1);
|
||||
#endif
|
||||
if(key == KEY_LEFT && c_mode == C_PAGE && c_page > 0)
|
||||
c_page--;
|
||||
if(key == KEY_RIGHT && c_mode == C_PAGE && c_page+1 < doc->page_count)
|
||||
c_page++;
|
||||
|
||||
if(key == KEY_EXIT && c_mode == C_DETAILS)
|
||||
c_mode = C_CHAPTER;
|
||||
if(key == KEY_EXIT && c_mode == C_PAGE)
|
||||
c_mode = C_DOCUMENT;
|
||||
if(key == KEY_SHIFT || key == KEY_EXE) {
|
||||
if(c_mode == C_CHAPTER) {
|
||||
if(c_mode == C_DOCUMENT) {
|
||||
if(doc->page_count > 0) {
|
||||
c_mode = C_PAGE;
|
||||
c_page = 0;
|
||||
}
|
||||
#if 0
|
||||
if(ch->subjects_size > 0) {
|
||||
c_mode = C_DETAILS;
|
||||
c_details = 0;
|
||||
c_mode = C_PAGE;
|
||||
c_page = 0;
|
||||
}
|
||||
else if(ch->questions_size > 0) {
|
||||
c_mode = C_DETAILS;
|
||||
c_details = 1;
|
||||
c_mode = C_PAGE;
|
||||
c_page = 1;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
read_chapter(ch, c_details & 1, c_details / 2);
|
||||
read_document(doc, c_page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
/* "Slides" structure, represents a document as a set of slides containing text
|
||||
and TeX formulas. */
|
||||
struct slides {
|
||||
char const *title;
|
||||
int size;
|
||||
struct slide *slides;
|
||||
};
|
||||
struct slide {
|
||||
int size;
|
||||
struct line *lines;
|
||||
};
|
||||
enum {
|
||||
LINE_TYPE_TEXT,
|
||||
LINE_TYPE_DISPLAY_MATH,
|
||||
};
|
||||
struct line {
|
||||
int type;
|
||||
char const *source;
|
||||
};
|
||||
|
||||
/* Chapter structure:
|
||||
- Slides for each course subject
|
||||
- Slides for each typical question */
|
||||
struct chapter {
|
||||
char const *title;
|
||||
int bgcolor;
|
||||
|
||||
int subjects_size;
|
||||
struct slides *subjects;
|
||||
|
||||
int questions_size;
|
||||
struct slides *questions;
|
||||
};
|
||||
|
||||
/* Start reading specified chapter, starting at the provided category (0 for
|
||||
subjects, 1 for questions) at position index (0 ≤ index < subjects_size or
|
||||
0 ≤ index < questions_size). */
|
||||
void read_chapter(struct chapter const *chapter, int category, int index);
|
||||
|
||||
/* Render slide buttons */
|
||||
void render_buttons(int x, int y, int count, int selected, int color,
|
||||
int spacing);
|
||||
|
|
@ -0,0 +1,528 @@
|
|||
/* Lexer and parser for TeX-like files for courses, questions, etc. */
|
||||
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include "document.h"
|
||||
|
||||
/* Token types. */
|
||||
enum {
|
||||
T_END = 0, /* EOF */
|
||||
T_METADATA, /* Metadata line "% <name>: <value>" */
|
||||
T_PARAGRAPH, /* Paragraph separator (double newline) */
|
||||
T_TEXT, /* Plain text */
|
||||
T_INLINEMATH, /* Inline math $...$ */
|
||||
T_DISPLAYMATH, /* Display math \[ ... \] */
|
||||
T_ERROR, /* Lexing error indicator */
|
||||
};
|
||||
struct token {
|
||||
int type; /* A T_* enumerated token type */
|
||||
char const *str1; /* Pointer to substring of input 1 */
|
||||
int len1; /* Length of substring 1 */
|
||||
char const *str2; /* Pointer to substring of input 2 */
|
||||
int len2; /* Length of substring 2 */
|
||||
};
|
||||
|
||||
struct lexer {
|
||||
char const *str; /* Input text */
|
||||
int len; /* Length of input string */
|
||||
int offset; /* Current offset */
|
||||
bool start_of_line; /* Whether we're at the start of a line */
|
||||
};
|
||||
|
||||
static void lexer_init(struct lexer *lexer, char const *str, int len)
|
||||
{
|
||||
lexer->str = str;
|
||||
lexer->len = len;
|
||||
lexer->offset = 0;
|
||||
lexer->start_of_line = true;
|
||||
}
|
||||
|
||||
static void lexer_deinit(struct lexer *lexer)
|
||||
{
|
||||
memset(lexer, 0, sizeof *lexer);
|
||||
}
|
||||
|
||||
/* Skip spaces, returns true if `end` is reached. */
|
||||
static bool skip_spaces(char const **str, char const *end)
|
||||
{
|
||||
while(*str < end && (**str == ' ' || **str == '\t'))
|
||||
(*str)++;
|
||||
return (*str >= end);
|
||||
}
|
||||
|
||||
/* ^%\s*([a-z]+):\s*(.*)$
|
||||
If the value (second group) is surrounded with '"', take them out. */
|
||||
static bool _lex_metadata(struct lexer *lexer, struct token *token)
|
||||
{
|
||||
char const *str = lexer->str + lexer->offset;
|
||||
char const *end = lexer->str + lexer->len;
|
||||
|
||||
if(str > end - 1 || !lexer->start_of_line || *str != '%')
|
||||
return false;
|
||||
str++;
|
||||
if(skip_spaces(&str, end))
|
||||
return false;
|
||||
|
||||
char const *name = str;
|
||||
while(str < end && islower(*str))
|
||||
str++;
|
||||
|
||||
if(str >= end || str == name)
|
||||
return false;
|
||||
int name_len = str - name;
|
||||
|
||||
if(skip_spaces(&str, end))
|
||||
return false;
|
||||
if(*str++ != ':')
|
||||
return false;
|
||||
if(skip_spaces(&str, end))
|
||||
return false;
|
||||
|
||||
char const *arg = str;
|
||||
while(str < end && *str != '\n')
|
||||
str++;
|
||||
|
||||
int arg_len = str - arg;
|
||||
if(arg_len >= 2 && arg[0] == '"' && arg[arg_len-1] == '"') {
|
||||
arg++;
|
||||
arg_len -= 2;
|
||||
}
|
||||
|
||||
lexer->offset = (str + (*str == '\n')) - lexer->str;
|
||||
lexer->start_of_line = (*str == '\n');
|
||||
|
||||
token->type = T_METADATA;
|
||||
token->str1 = name;
|
||||
token->len1 = name_len;
|
||||
token->str2 = arg;
|
||||
token->len2 = arg_len;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* \[\s*(.*+)\s*\] */
|
||||
static bool _lex_displaymath(struct lexer *lexer, struct token *token)
|
||||
{
|
||||
char const *str = lexer->str + lexer->offset;
|
||||
char const *end = lexer->str + lexer->len;
|
||||
|
||||
if(!lexer->start_of_line || skip_spaces(&str, end))
|
||||
return false;
|
||||
if(str > end - 2 || str[0] != '\\' || str[1] != '[')
|
||||
return false;
|
||||
str += 2;
|
||||
if(skip_spaces(&str, end))
|
||||
return false;
|
||||
|
||||
char const *formula = str;
|
||||
while(str <= end - 2 && (str[0] != '\\' || str[1] != ']'))
|
||||
str++;
|
||||
if(str > end - 2 || str[0] != '\\' || str[1] != ']')
|
||||
return false;
|
||||
|
||||
/* Trim spaces on the right */
|
||||
int formula_len = str - formula;
|
||||
while(formula_len > 0 && (formula[formula_len - 1] == ' ' ||
|
||||
formula[formula_len - 1] == '\t'))
|
||||
formula_len--;
|
||||
|
||||
str += 2;
|
||||
skip_spaces(&str, end);
|
||||
bool end_of_line = (str < end && *str == '\n');
|
||||
|
||||
lexer->offset = (str + end_of_line) - lexer->str;
|
||||
lexer->start_of_line = end_of_line;
|
||||
|
||||
token->type = T_DISPLAYMATH;
|
||||
token->str1 = formula;
|
||||
token->len1 = formula_len;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* \n+, emits a T_PARAGRAPH for \n{2,} but always skips, even one */
|
||||
static bool _lex_par(struct lexer *lexer, struct token *token)
|
||||
{
|
||||
char const *str = lexer->str + lexer->offset;
|
||||
char const *end = lexer->str + lexer->len;
|
||||
|
||||
if(str > end - 1 || *str != '\n')
|
||||
return false;
|
||||
|
||||
/* Count newlines */
|
||||
int newline_count = 0;
|
||||
|
||||
while(str < end && *str == '\n')
|
||||
str++, newline_count++;
|
||||
|
||||
lexer->offset = str - lexer->str;
|
||||
lexer->start_of_line = true;
|
||||
|
||||
if(newline_count < 2)
|
||||
return false;
|
||||
|
||||
token->type = T_PARAGRAPH;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* $\s*(.*+)\s*$ */
|
||||
static bool _lex_inlinemath(struct lexer *lexer, struct token *token)
|
||||
{
|
||||
char const *str = lexer->str + lexer->offset;
|
||||
char const *end = lexer->str + lexer->len;
|
||||
|
||||
if(str > end - 1 || *str != '$')
|
||||
return false;
|
||||
|
||||
str++;
|
||||
char const *formula = str;
|
||||
while(str <= end - 1 && *str != '$')
|
||||
str++;
|
||||
if(str > end - 1 || str == formula)
|
||||
return false;
|
||||
int formula_len = str - formula;
|
||||
|
||||
/* Trim spaces on both sides */
|
||||
while(formula_len > 0 && formula[0] == ' ') {
|
||||
formula++;
|
||||
formula_len--;
|
||||
}
|
||||
while(formula_len > 0 && formula[formula_len-1] == ' ')
|
||||
formula_len--;
|
||||
|
||||
lexer->offset = (str + 1) - lexer->str;
|
||||
lexer->start_of_line = false;
|
||||
|
||||
token->type = T_INLINEMATH;
|
||||
token->str1 = formula;
|
||||
token->len1 = formula_len;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _lex_text(struct lexer *lexer, struct token *token)
|
||||
{
|
||||
char const *str = lexer->str + lexer->offset;
|
||||
char const *end = lexer->str + lexer->len;
|
||||
|
||||
char const *text = str;
|
||||
while(str <= end - 1 && *str != '\n' && *str != '$')
|
||||
str++;
|
||||
int text_len = str - text;
|
||||
|
||||
if(text_len == 0)
|
||||
return false;
|
||||
|
||||
lexer->offset = str - lexer->str;
|
||||
lexer->start_of_line = false;
|
||||
|
||||
token->type = T_TEXT;
|
||||
token->str1 = text;
|
||||
token->len1 = text_len;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void _lex(struct lexer *lexer, struct token *token)
|
||||
{
|
||||
memset(token, 0, sizeof *token);
|
||||
|
||||
/* Has the side-effect of skipping incoming newlines even when no match */
|
||||
if(_lex_par(lexer, token))
|
||||
return;
|
||||
|
||||
if(lexer->offset >= lexer->len) {
|
||||
token->type = T_END;
|
||||
return;
|
||||
}
|
||||
|
||||
if(_lex_metadata(lexer, token))
|
||||
return;
|
||||
if(_lex_displaymath(lexer, token))
|
||||
return;
|
||||
if(_lex_inlinemath(lexer, token))
|
||||
return;
|
||||
if(_lex_text(lexer, token))
|
||||
return;
|
||||
|
||||
/* Default to an error on one character */
|
||||
lexer->start_of_line = (lexer->str[lexer->offset] == '\n');
|
||||
lexer->offset++;
|
||||
|
||||
token->type = T_ERROR;
|
||||
token->str1 = lexer->str + lexer->offset - 1;
|
||||
token->len1 = 1;
|
||||
}
|
||||
|
||||
#if 0
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static void dump_tokens(char const *str, int len)
|
||||
{
|
||||
struct lexer l;
|
||||
lexer_init(&l, str, len);
|
||||
|
||||
char const *T_names[] = {
|
||||
"T_END", "T_METADATA", "T_PARAGRAPH", "T_TEXT", "T_INLINEMATH",
|
||||
"T_DISPLAYMATH", "T_ERROR",
|
||||
};
|
||||
int T_names_size = sizeof T_names / sizeof T_names[0];
|
||||
|
||||
struct token t;
|
||||
do {
|
||||
_lex(&l, &t);
|
||||
|
||||
if(t.type >= 0 && t.type < T_names_size)
|
||||
printf("%s", T_names[t.type]);
|
||||
else
|
||||
printf("<%d>", t.type);
|
||||
|
||||
if(t.str1)
|
||||
printf(": `%.*s`", t.len1, t.str1);
|
||||
if(t.str2)
|
||||
printf(", `%.*s`", t.len2, t.str2);
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
while(t.type != T_END);
|
||||
|
||||
lexer_deinit(&l);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
FILE *fp = fopen(argv[1], "r");
|
||||
if(!fp) {
|
||||
perror("fopen");
|
||||
return 1;
|
||||
}
|
||||
|
||||
fseek(fp, 0, SEEK_END);
|
||||
int len = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
|
||||
char *str = malloc(len);
|
||||
fread(str, 1, len, fp);
|
||||
fclose(fp);
|
||||
|
||||
dump_tokens(str, len);
|
||||
|
||||
free(str);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct parser {
|
||||
struct lexer *lexer;
|
||||
/* Look-ahead token */
|
||||
struct token la;
|
||||
};
|
||||
|
||||
static void parser_init(struct parser *p, struct lexer *l)
|
||||
{
|
||||
p->lexer = l;
|
||||
_lex(l, &p->la);
|
||||
}
|
||||
|
||||
static void parser_deinit(struct parser *p)
|
||||
{
|
||||
lexer_deinit(p->lexer);
|
||||
}
|
||||
|
||||
static struct token feed(struct parser *p)
|
||||
{
|
||||
struct token t = p->la;
|
||||
_lex(p->lexer, &p->la);
|
||||
return t;
|
||||
}
|
||||
|
||||
static bool is_metadata(struct token const *t, char const *kind)
|
||||
{
|
||||
return t->type == T_METADATA
|
||||
&& t->len1 == (int)strlen(kind)
|
||||
&& !strncmp(t->str1, kind, t->len1);
|
||||
}
|
||||
|
||||
static bool accept(struct parser *p, int type, struct token *t)
|
||||
{
|
||||
if(p->la.type == type) {
|
||||
*t = feed(p);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool accept_metadata(struct parser *p, char const *kind,
|
||||
struct token *t)
|
||||
{
|
||||
if(is_metadata(&p->la, kind)) {
|
||||
*t = feed(p);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool is_inline(struct token const *t)
|
||||
{
|
||||
return (t->type == T_TEXT || t->type == T_INLINEMATH);
|
||||
}
|
||||
|
||||
struct page *_parse_page(struct parser *p)
|
||||
{
|
||||
struct page *page = page_new();
|
||||
if(!page)
|
||||
return NULL;
|
||||
|
||||
/* Find page title */
|
||||
struct token t;
|
||||
if(accept_metadata(p, "cours", &t)) {
|
||||
page_set_title(page, t.str2, t.len2);
|
||||
page_set_category(page, "Cours", -1);
|
||||
}
|
||||
else if(accept_metadata(p, "question", &t)) {
|
||||
page_set_title(page, t.str2, t.len2);
|
||||
page_set_category(page, "Questions", -1);
|
||||
}
|
||||
else goto error;
|
||||
|
||||
/* Now accept any number of elements */
|
||||
int last_type_was_inline = false;
|
||||
while(1) {
|
||||
struct token t;
|
||||
|
||||
if(accept(p, T_TEXT, &t)) {
|
||||
struct element *e = element_new_inline_text(t.str1, t.len1);
|
||||
page_add_element(page, e);
|
||||
}
|
||||
else if(accept(p, T_INLINEMATH, &t)) {
|
||||
struct element *e = element_new_inline_math(t.str1, t.len1);
|
||||
page_add_element(page, e);
|
||||
}
|
||||
else if(accept(p, T_DISPLAYMATH, &t)) {
|
||||
struct element *e = element_new_block_math(t.str1, t.len1);
|
||||
page_add_element(page, e);
|
||||
}
|
||||
else if(accept(p, T_PARAGRAPH, &t)) {
|
||||
if(last_type_was_inline && is_inline(&p->la)) {
|
||||
struct element *e = element_new_new_paragraph();
|
||||
page_add_element(page, e);
|
||||
}
|
||||
}
|
||||
else break;
|
||||
|
||||
last_type_was_inline = is_inline(&t);
|
||||
}
|
||||
|
||||
return page;
|
||||
|
||||
error:
|
||||
page_free(page);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct document *document_parse(char const *str, int len)
|
||||
{
|
||||
struct lexer l;
|
||||
lexer_init(&l, str, len);
|
||||
struct parser p;
|
||||
parser_init(&p, &l);
|
||||
|
||||
struct document *doc = document_new();
|
||||
|
||||
while(p.la.type != T_END) {
|
||||
struct token t;
|
||||
|
||||
if(accept_metadata(&p,"chapitre", &t)) {
|
||||
document_set_title(doc, t.str2, t.len2);
|
||||
continue;
|
||||
}
|
||||
if(accept_metadata(&p,"symbole", &t)) {
|
||||
document_set_symbol(doc, t.str2, t.len2);
|
||||
continue;
|
||||
}
|
||||
if(accept_metadata(&p,"bg", &t)) {
|
||||
// TODO: Not very clean (can overflow, in principle)
|
||||
document_set_bg(doc, strtol(t.str2, NULL, 0));
|
||||
continue;
|
||||
}
|
||||
|
||||
if(is_metadata(&p.la, "cours") || is_metadata(&p.la, "question")) {
|
||||
struct page *page = _parse_page(&p);
|
||||
if(page)
|
||||
document_add_page(doc, page);
|
||||
continue;
|
||||
}
|
||||
|
||||
goto error;
|
||||
}
|
||||
|
||||
parser_deinit(&p);
|
||||
return doc;
|
||||
|
||||
error:
|
||||
document_free(doc);
|
||||
lexer_deinit(&l);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if 0
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static void dump_document(struct document const *doc)
|
||||
{
|
||||
if(!doc) {
|
||||
printf("(null)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("document:\n");
|
||||
printf(" title: `%s`\n", doc->title);
|
||||
printf(" symbol: `%s`\n", doc->symbol);
|
||||
printf(" bgcolor: 0x%04x\n", doc->bgcolor);
|
||||
printf(" pages:\n");
|
||||
|
||||
for(int i = 0; i < doc->page_count; i++) {
|
||||
struct page *page = doc->pages[i];
|
||||
printf(" - title: `%s`\n", page->title);
|
||||
printf(" category: `%s`\n", page->category);
|
||||
printf(" elements:\n");
|
||||
|
||||
for(int j = 0; j < page->element_count; j++) {
|
||||
struct element *e = page->elements[j];
|
||||
printf(" - ");
|
||||
if(e->type == ELEMENT_INLINE_TEXT)
|
||||
printf("INLINE_TEXT: `%s`\n", e->str);
|
||||
else if(e->type == ELEMENT_INLINE_MATH)
|
||||
printf("INLINE_MATH: `%s`\n", e->str);
|
||||
else if(e->type == ELEMENT_BLOCK_MATH)
|
||||
printf("BLOCK_MATH: `%s`\n", e->str);
|
||||
else if(e->type == ELEMENT_NEW_PARAGRAPH)
|
||||
printf("NEW_PARAGRAPH\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
FILE *fp = fopen(argv[1], "r");
|
||||
if(!fp) {
|
||||
perror("fopen");
|
||||
return 1;
|
||||
}
|
||||
|
||||
fseek(fp, 0, SEEK_END);
|
||||
int len = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
|
||||
char *str = malloc(len);
|
||||
fread(str, 1, len, fp);
|
||||
fclose(fp);
|
||||
|
||||
struct document *doc = document_parse(str, len);
|
||||
dump_document(doc);
|
||||
document_free(doc);
|
||||
|
||||
free(str);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
174
src/reader.c
174
src/reader.c
|
@ -4,7 +4,8 @@
|
|||
#include <TeX/TeX.h>
|
||||
#include <TeX/env.h>
|
||||
#include <string.h>
|
||||
#include "mathsts.h"
|
||||
#include "reader.h"
|
||||
#include "document.h"
|
||||
#include "brk.h"
|
||||
|
||||
static bool debug = false;
|
||||
|
@ -38,20 +39,6 @@ static void fkey_button(int position, char const *text, int bg)
|
|||
dtext(x + ((w - width) >> 1), y + 3, C_WHITE, text);
|
||||
}
|
||||
|
||||
static char const *find_dollar(char const *str, char const **brk, bool skip_it)
|
||||
{
|
||||
/* Find the first unescaped '$' */
|
||||
while(*str) {
|
||||
if(*str == '$') {
|
||||
*brk = str + (skip_it == true);
|
||||
return str;
|
||||
}
|
||||
str = str + 1 + (str[0] == '\\' && str[1] != 0);
|
||||
}
|
||||
*brk = str;
|
||||
return str;
|
||||
}
|
||||
|
||||
static int rtl_w = -1;
|
||||
static struct TeX_Env *rtl_formulas[32] = { NULL };
|
||||
|
||||
|
@ -81,43 +68,63 @@ static void rtl_rbf(int x, int y, unsigned int id)
|
|||
TeX_draw(rtl_formulas[id], x, y, C_BLACK);
|
||||
}
|
||||
|
||||
static int render_text_line(int x, int y, int w, char const *source,
|
||||
static int render_page(int x, int y, int w, struct page const *page,
|
||||
font_t const *font, int wrap_mode)
|
||||
{
|
||||
char const *next;
|
||||
int formula_count = 0;
|
||||
|
||||
rtl_w = w;
|
||||
struct brk_state *brk = brk_new(x, y, rtl_lwf, rtl_rtf, rtl_rbf);
|
||||
|
||||
/* Each loop iteration is a either a text segment or a formula */
|
||||
while(*source) {
|
||||
/* Formula */
|
||||
if(*source == '$') {
|
||||
source++;
|
||||
char const *end = find_dollar(source, &next, true);
|
||||
if(formula_count < 32) {
|
||||
char *formula = strndup(source, end-source);
|
||||
struct TeX_Env *env = TeX_parse(formula, false);
|
||||
free(formula);
|
||||
rtl_formulas[formula_count] = env;
|
||||
// TODO: Adjustment between text baseline and formula baseline
|
||||
// hardcoded here
|
||||
/* Keep one pixel of padding on the right for punctuation */
|
||||
brk_layout_block(brk, formula_count, env->width+1, env->height,
|
||||
env->line + 5);
|
||||
formula_count++;
|
||||
}
|
||||
source = next;
|
||||
for(int i = 0; i < page->element_count; i++) {
|
||||
struct element const *e = page->elements[i];
|
||||
|
||||
if(e->type == ELEMENT_INLINE_MATH && formula_count < 32) {
|
||||
struct TeX_Env *env = TeX_parse(e->str, false);
|
||||
rtl_formulas[formula_count] = env;
|
||||
// TODO: Adjustment between text baseline and formula baseline
|
||||
// hardcoded here
|
||||
/* Keep one pixel of padding on the right for punctuation */
|
||||
brk_layout_block(brk, formula_count, env->width+1, env->height,
|
||||
env->line + 5);
|
||||
formula_count++;
|
||||
}
|
||||
/* Text */
|
||||
else {
|
||||
char const *end = find_dollar(source, &next, false);
|
||||
brk_layout_text(brk, source, end-source, font, wrap_mode, 0);
|
||||
source = next;
|
||||
else if(e->type == ELEMENT_INLINE_TEXT) {
|
||||
brk_layout_text(brk, e->str, -1, font, wrap_mode, 0);
|
||||
}
|
||||
else if(e->type == ELEMENT_BLOCK_MATH && formula_count < 32) {
|
||||
struct TeX_Env *env = TeX_parse(e->str, true);
|
||||
rtl_formulas[formula_count] = env;
|
||||
// TODO: Center these
|
||||
brk_layout_newline(brk);
|
||||
brk_layout_block(brk, formula_count, env->width, env->height,
|
||||
env->line);
|
||||
brk_layout_newline(brk);
|
||||
formula_count++;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
else if(line->type == LINE_TYPE_DISPLAY_MATH) {
|
||||
struct TeX_Env *env = TeX_parse(line->source, true);
|
||||
if(!env) { ... }
|
||||
|
||||
int cx = x + w / 2;
|
||||
int ex = cx - env->width / 2;
|
||||
int ey = y + 2;
|
||||
|
||||
if(debug) {
|
||||
drect(ex, ey, ex+env->width-1, ey+env->height-1,
|
||||
C_RGB(23, 23, 23));
|
||||
dline(ex, ey+env->line, ex+env->width-1, ey+env->line,
|
||||
C_RED);
|
||||
}
|
||||
TeX_draw(env, cx - env->width / 2, y+2, C_BLACK);
|
||||
TeX_free(env);
|
||||
y += env->height + 5;
|
||||
}
|
||||
#endif
|
||||
|
||||
brk_layout_finish(brk);
|
||||
int h = brk_get_h(brk);
|
||||
|
||||
|
@ -131,56 +138,6 @@ static int render_text_line(int x, int y, int w, char const *source,
|
|||
return h;
|
||||
}
|
||||
|
||||
static int render(int x, int y0, int w, int h, struct slides const *slides,
|
||||
int scroll)
|
||||
{
|
||||
int y = y0 - scroll;
|
||||
(void)h;
|
||||
|
||||
for(int i = 0; i < slides->size; i++) {
|
||||
struct slide const *slide = &slides->slides[i];
|
||||
for(int j = 0; j < slide->size; j++) {
|
||||
struct line const *line = &slide->lines[j];
|
||||
if(line->type == LINE_TYPE_TEXT) {
|
||||
int h = render_text_line(x+4, y, w-8, line->source, NULL,
|
||||
BRK_WRAP_WORD);
|
||||
y += h;
|
||||
}
|
||||
else if(line->type == LINE_TYPE_DISPLAY_MATH) {
|
||||
struct TeX_Env *env = TeX_parse(line->source, true);
|
||||
int cx = x + w / 2;
|
||||
|
||||
if(env) {
|
||||
int ex = cx - env->width / 2;
|
||||
int ey = y + 2;
|
||||
|
||||
if(debug) {
|
||||
drect(ex, ey, ex+env->width-1, ey+env->height-1,
|
||||
C_RGB(23, 23, 23));
|
||||
dline(ex, ey+env->line, ex+env->width-1, ey+env->line,
|
||||
C_RED);
|
||||
}
|
||||
TeX_draw(env, cx - env->width / 2, y+2, C_BLACK);
|
||||
TeX_free(env);
|
||||
y += env->height + 5;
|
||||
}
|
||||
else {
|
||||
dtext_opt(cx, y, C_RED, C_NONE, DTEXT_CENTER, DTEXT_TOP,
|
||||
"[formula parse error]", -1);
|
||||
y += 13;
|
||||
}
|
||||
}
|
||||
else {
|
||||
dprint(x+20, y, C_BLACK, "(%d) \"%s\"", line->type,
|
||||
line->source);
|
||||
y += 13;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return y - (y0 - scroll);
|
||||
}
|
||||
|
||||
static void scrollbar(int x, int y, int scroll, int content_height,
|
||||
int display_height)
|
||||
{
|
||||
|
@ -193,18 +150,21 @@ static void scrollbar(int x, int y, int scroll, int content_height,
|
|||
drect(x, y+top, x+1, y+top+height-1, C_BLACK);
|
||||
}
|
||||
|
||||
void read_chapter(struct chapter const *chapter, int category, int index)
|
||||
void read_document(struct document const *doc, int page_index)
|
||||
{
|
||||
int scroll = 0;
|
||||
int maxscroll = 0;
|
||||
|
||||
while(1) {
|
||||
struct page const *page = doc->pages[page_index];
|
||||
|
||||
dclear(C_WHITE);
|
||||
drect(4, 4, DWIDTH-1-4, 44, chapter->bgcolor);
|
||||
drect(4, 4, DWIDTH-1-4, 44, doc->bgcolor);
|
||||
drect_border(5, 5, DWIDTH-1-5, 43, C_NONE, 1, C_WHITE);
|
||||
dtext(12, 12, C_WHITE, chapter->title);
|
||||
dtext(12, 12, C_WHITE, doc->title);
|
||||
|
||||
int spacing = 9;
|
||||
#if 0
|
||||
int total = chapter->subjects_size + chapter->questions_size;
|
||||
render_small_buttons(DWIDTH - 18 - spacing * total, 16,
|
||||
chapter->subjects_size, chapter->questions_size,
|
||||
|
@ -216,6 +176,12 @@ void read_chapter(struct chapter const *chapter, int category, int index)
|
|||
category ? chapter->questions_size : chapter->subjects_size,
|
||||
category ? chapter->questions[index].title
|
||||
: chapter->subjects[index].title);
|
||||
#endif
|
||||
render_small_buttons(DWIDTH - 18 - spacing * doc->page_count, 16,
|
||||
doc->page_count, 0, 0, page_index, C_WHITE,
|
||||
spacing);
|
||||
dprint(12, 27, C_WHITE,
|
||||
"%d/%d : %s", page_index + 1, doc->page_count, page->title);
|
||||
|
||||
if(debug) {
|
||||
kmalloc_arena_t *arena = kmalloc_get_arena("_uram");
|
||||
|
@ -224,12 +190,16 @@ void read_chapter(struct chapter const *chapter, int category, int index)
|
|||
DTEXT_TOP, "heap: %d", stats->free_memory);
|
||||
}
|
||||
|
||||
#if 0
|
||||
bool can_go_left =
|
||||
(index > 0) || (category > 0 && chapter->subjects_size > 0);
|
||||
bool can_go_right =
|
||||
(category == 1 && index < chapter->questions_size - 1)
|
||||
|| (category == 0 && (index < chapter->questions_size - 1
|
||||
|| chapter->questions_size > 0));
|
||||
#endif
|
||||
bool can_go_left = (page_index > 0);
|
||||
bool can_go_right = (page_index < doc->page_count - 1);
|
||||
|
||||
fkey_button(5, "PRÉC.", can_go_left ? C_BLACK : C_RGB(16, 16, 16));
|
||||
fkey_button(6, "SUIV.", can_go_right ? C_BLACK : C_RGB(16, 16, 16));
|
||||
|
@ -243,10 +213,8 @@ void read_chapter(struct chapter const *chapter, int category, int index)
|
|||
struct dwindow old_w = dwindow_set(w);
|
||||
|
||||
int dh = w.bottom - w.top - 5;
|
||||
int ch = render(w.left+3, w.top+3, w.right-w.left-5, dh,
|
||||
category ? &chapter->questions[index]
|
||||
: &chapter->subjects[index],
|
||||
scroll);
|
||||
int ch = render_page(w.left + 3, w.top + 3 - scroll,
|
||||
w.right - w.left - 5, page, NULL, BRK_WRAP_WORD);
|
||||
|
||||
scrollbar(w.right-2, w.top+3, scroll, ch, dh);
|
||||
maxscroll = (ch >= dh) ? ch - dh : 0;
|
||||
|
@ -258,6 +226,7 @@ void read_chapter(struct chapter const *chapter, int category, int index)
|
|||
if(key == KEY_EXIT)
|
||||
break;
|
||||
|
||||
#if 0
|
||||
if(key == KEY_F5 && can_go_left) {
|
||||
index--;
|
||||
if(index < 0) {
|
||||
|
@ -274,6 +243,15 @@ void read_chapter(struct chapter const *chapter, int category, int index)
|
|||
}
|
||||
scroll = 0;
|
||||
}
|
||||
#endif
|
||||
if(key == KEY_F5 && can_go_left) {
|
||||
page_index--;
|
||||
scroll = 0;
|
||||
}
|
||||
if(key == KEY_F6 && can_go_right) {
|
||||
page_index++;
|
||||
scroll = 0;
|
||||
}
|
||||
if(key == KEY_OPTN)
|
||||
debug = !debug;
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "document.h"
|
||||
|
||||
/* Start reading specified document, starting at the provided page (in bounds
|
||||
0 ≤ page_index < doc->page_count). */
|
||||
void read_document(struct document const *document, int page_index);
|
Loading…
Reference in New Issue