From 44549f132f4897ae7549356835cac52d0fa4f318 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Tue, 28 May 2019 22:11:05 -0400 Subject: [PATCH] initial commit: basic parsing, flow, rendering and some nodes --- .gitignore | 19 ++ Makefile | 94 +++++++ TODO | 11 + config/cli.cfg | 4 + config/fx9860g.cfg | 5 + config/fxcg50.cfg | 5 + config/sdl2.cfg | 6 + configure | 99 +++++++ include/TeX/TeX.h | 153 +++++++++++ include/TeX/defs.h | 23 ++ include/TeX/parser.h | 44 +++ include/TeX/render.h | 23 ++ include/TeX/structure.h | 153 +++++++++++ src/TeX.c | 147 ++++++++++ src/TeX.y | 589 ++++++++++++++++++++++++++++++++++++++++ src/classes.c | 255 +++++++++++++++++ src/flow.c | 314 +++++++++++++++++++++ src/platform/cli.c | 112 ++++++++ src/platform/sdl2.c | 143 ++++++++++ 19 files changed, 2199 insertions(+) create mode 100644 .gitignore create mode 100755 Makefile create mode 100644 TODO create mode 100644 config/cli.cfg create mode 100644 config/fx9860g.cfg create mode 100644 config/fxcg50.cfg create mode 100644 config/sdl2.cfg create mode 100755 configure create mode 100644 include/TeX/TeX.h create mode 100644 include/TeX/defs.h create mode 100644 include/TeX/parser.h create mode 100644 include/TeX/render.h create mode 100644 include/TeX/structure.h create mode 100644 src/TeX.c create mode 100644 src/TeX.y create mode 100644 src/classes.c create mode 100644 src/flow.c create mode 100644 src/platform/cli.c create mode 100644 src/platform/sdl2.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..62b5ff6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# Files generated by configuration +Makefile.cfg + +# Other working files +exclude/ + +# Build files +build/ + +# Target files +TeX-cli +TeX-SDL +libTeX-fx.a +libTeX-cg.a + +# Sublime Text files +*.sublime-workspace +*.sublime-project + diff --git a/Makefile b/Makefile new file mode 100755 index 0000000..d1330a4 --- /dev/null +++ b/Makefile @@ -0,0 +1,94 @@ +#! /usr/bin/make -f + +# Require the configuration file if we're not cleaning +ifeq "$(filter clean distclean,$(MAKECMDGOALS))" "" +ifeq ($(wildcard Makefile.cfg),) +$(error "Makefile.cfg is missing. Did you ./configure ?") +endif +endif + +include Makefile.cfg +include config/$(PLATFORM).cfg + +parser := src/TeX.y +src := $(wildcard src/*.c) $(wildcard src/platform/$(PLATFORM).c) +obj := $(src:src/%.c=build/%.c.o) $(parser:src/%.y=build/%.tab.c.o) + +cflags := -Wall -Wextra -std=c11 -I include -D_GNU_SOURCE $(CFLAGS) +dflags = -MMD -MT $@ -MF $(@:.o=.d) -MP +bflags := -L C + +# +# Building +# + +all: $(TARGET) + +TeX-SDL TeX-cli: $(obj) + $(CC) $^ -o $@ $(cflags) $(LDFLAGS) + +libTeX-fx.a libTeX-cg.a: $(obj) + $(AR) rcs $@ $^ + +build/%.c.o: src/%.c Makefile.cfg + @ mkdir -p $(dir $@) + $(CC) -c $< -o $@ $(dflags) $(cflags) +build/%.tab.c.o: build/%.tab.c + @ mkdir -p $(dir $@) + $(CC) -c $< -o $@ $(dflags) $(cflags) + +build/%.tab.c: src/%.y Makefile.cfg + @ mkdir -p $(dir $@) + bison $< -o $@ $(bflags) + +# +# Dependency management +# + + +.PRECIOUS: build/%.tab.c + +.PRECIOUS: build/%.d + +build/%.d: ; + +include $(wildcard build/*.d) + +# +# Install +# + +ifeq "$(PLATFORM)" "fx9860g" +install: $(TARGET) + install -d $(PREFIX) + install $(TARGET) -m 644 $(PREFIX) + cp -r include/TeX $(PREFIX)/include + +uninstall: $(TARGET) + rm -f $(PREFIX)/$(TARGET) + rm -rf $(PREFIX)/include/TeX +endif + +ifeq "$(PLATFORM)" "fxcg50" +install: $(TARGET) + install -d $(PREFIX) + install $(TARGET) -m 644 $(PREFIX) + cp -r include/TeX $(PREFIX)/include + +uninstall: $(TARGET) + rm -f $(PREFIX)/$(TARGET) + rm -rf $(PREFIX)/include/TeX +endif + +# +# Cleaning +# + +clean: + rm -rf build +distclean: clean + rm -f Makefile.cfg + rm -f TeX-cli TeX-SDL libTeX-fx.a libTeX-cg.a + +.PHONY: all clean distclean install +.SUFFIXES: diff --git a/TODO b/TODO new file mode 100644 index 0000000..70823f0 --- /dev/null +++ b/TODO @@ -0,0 +1,11 @@ +* Support angle brackets, and honor TEX_LEFTRIGHT_MAX_ANGLE +* Write real error messages +* Support Unicode symbols (probably requires help from the font side, urgh) +* Don't use TEX_LAYOUT_SPACING for everything, just make it the default + +* Add sum and products +* Add square roots, and honor TEX_SQRT_SLANTED and TEX_SQRT_BAR_LENGTH +* Add vectors +* Add limits +* Add integrals, and honor TEX_INT_DISPLAY +* Add matrices diff --git a/config/cli.cfg b/config/cli.cfg new file mode 100644 index 0000000..fc847e8 --- /dev/null +++ b/config/cli.cfg @@ -0,0 +1,4 @@ +# Simple command-line parsing and layout troubleshooter +TARGET = TeX-cli +CFLAGS = -D TEX_PLATFORM_CLI -D TEX_DEBUG -g +CC = gcc diff --git a/config/fx9860g.cfg b/config/fx9860g.cfg new file mode 100644 index 0000000..f6b0af1 --- /dev/null +++ b/config/fx9860g.cfg @@ -0,0 +1,5 @@ +# Casio fx9860g library +TARGET = libTeX-fx.a +CFLAGS = -m3 -mb -D TEX_PLATFORM_FX9860G -D FX9860G -Os -ffreestanding +CC = sh3eb-elf-gcc +AR = sh3eb-elf-ar diff --git a/config/fxcg50.cfg b/config/fxcg50.cfg new file mode 100644 index 0000000..ae569d4 --- /dev/null +++ b/config/fxcg50.cfg @@ -0,0 +1,5 @@ +# Casio fxcg50 library +TARGET = libTeX-cg.a +CFLAGS = -m4-nofpu -mb -D TEX_PLATFORM_FXCG50 -D FXCG50 -Os -ffreestanding +CC = sh4eb-elf-gcc +AR = sh4eb-elf-ar diff --git a/config/sdl2.cfg b/config/sdl2.cfg new file mode 100644 index 0000000..5412cb4 --- /dev/null +++ b/config/sdl2.cfg @@ -0,0 +1,6 @@ +# SDL2 layout and rendering (not interactive) +SRC = main-SDL.c +TARGET = TeX-SDL +CFLAGS = -D TEX_PLATFORM_SDL -D TEX_DEBUG -g +LDFLAGS = -lSDL2 +CC = gcc diff --git a/configure b/configure new file mode 100755 index 0000000..1639933 --- /dev/null +++ b/configure @@ -0,0 +1,99 @@ +#! /usr/bin/bash + +usage() { + echo "usage: $0 --platform= [options...]" + echo "" + echo "Available platforms:" + + for file in config/*.cfg; do + platform="${file#config/}" + platform="${platform%.cfg}" + + printf ' %-10s' "$platform" + head -1 "$file" | sed 's/^# *//' + done + + echo "" + echo "Options for fx9860g and fxcg50:" + echo " --toolchain=" + echo " Target triplet; default is 'sh3eb-elf' for fx9860g and 'sh4eb-elf'" + echo " for fxcg50" + echo " --prefix=" + echo " Library install path, guessed automatically by asking the compiler" + echo " if not specified" + + exit ${1:-1} +} + +platform= +toolchain= + +# Parse command-line arguments + +for arg in "$@"; do case "$arg" in + -h|--help|"-?") + usage 0;; + --platform=*) + platform=${arg#*=};; + --toolchain=*) + toolchain=${arg#*=};; + --prefix=*) + prefix=${arg#*=};; + *) + echo "error: unrecognized option '$arg'" 2>&1 + error=1;; +esac; done + +# Check validity + +if [ -z "$platform" ]; then + echo "error: please specify a platform" 2>&1 + echo "Try '$0 --help' for more information." + exit 1 +fi + +if [ ! -f "config/$platform.cfg" ]; then + echo "error: unknown platform '$platform'" 2>&1 + echo "Try '$0 --help' for a list of available platforms." + exit 1 +fi + +# Guess toolchains + +[[ $platform == fx9860g && -z $toolchain ]] && toolchain=sh3eb-elf +[[ $platform == fxcg50 && -z $toolchain ]] && toolchain=sh4eb-elf + +# Guess compiler paths + +if [[ ( $platform == fx9860g || $platform == fxcg50 ) && -z $prefix ]]; then + echo "No prefix specified, let's ask the compiler:" + echo " $toolchain-gcc --print-search-dirs | grep install | sed 's/install: //'" + inst=$($toolchain-gcc --print-search-dirs | grep install | sed 's/install: //') + + if [[ $? != 0 ]]; then + echo "Call returned $?, giving up." + fi + + echo "Got '$inst'". + + if [[ ! -d $inst ]]; then + echo "Directory does not exist (or is not a directory), giving up." + fi + + echo "" + prefix=$inst +fi + +# Output configuration + +output_config() { + echo "PLATFORM=$platform" + + if [[ $platform == fx9860g || $platform == fxcg50 ]]; then + echo "TOOLCHAIN=$toolchain" + echo "PREFIX=$prefix" + fi +} + +output_config > Makefile.cfg +echo "Makefile.cfg now links to the platform file 'config/$platform.cfg'." diff --git a/include/TeX/TeX.h b/include/TeX/TeX.h new file mode 100644 index 0000000..0e2f8b3 --- /dev/null +++ b/include/TeX/TeX.h @@ -0,0 +1,153 @@ +//--- +// TeX: Natural rendering for mathematical formulae +//--- + +#ifndef TEX_H +#define TEX_H + +#include + +//--- +// Graphical settings +// A few quirks that can be adjusted to your needs. +//--- + +/* Thickness and horizontal margin of fraction bars */ +#define TEX_FRACTION_BAR_THICKNESS 1 +#define TEX_FRACTION_BAR_MARGIN 1 + +/* Vertical placement of subscripts and superscripts (relative to object) */ +#define TEX_SUBSCRIPT_ELEVATION 3 +#define TEX_SUPERSCRIPT_DEPTH 3 + +/* Align the center of the resizable brackets with the baseline */ +#define TEX_LEFTRIGHT_ALIGNED 0 +/* Make them extend symmetrically around baseline */ +#define TEX_LEFTRIGHT_SYMMETRICAL 0 +/* Maximum angle for resizable "<" and ">" delimiters (radians) */ +#define TEX_LEFTRIGHT_MAX_ANGLE 2.50 + +/* Spacing: between layout elements, letters, and width of space character */ +#define TEX_LAYOUT_SPACING 1 +#define TEX_LETTER_SPACING 1 +#define TEX_WORD_SPACING 6 + +/* Whether to place bounds above and below integrals (display mode) */ +#define TEX_INT_DISPLAY 0 + +/* Make the vertical part of the square root symbol slanted at low heights */ +#define TEX_SQRT_SLANTED 1 +/* Length of the top-right bar of square roots (0 to disable) */ +#define TEX_SQRT_BAR_LENGTH 0 + +//--- +// Implementation settings +//--- + +/* Size of the lexer buffer; must be large enough to hold all command names. + Longer than the longest literal string is a waste of space */ +#define TEX_LEXER_BUFSIZE 64 + +/* Maximum number of nested left/right pairs */ +#define TEX_LEFTRIGHT_NESTING 16 + +/* Enable or disable debugging functions with TEX_DEBUG. Currently this is + enabled (and used) by all computer targets and disabled on calculator. */ +#if defined(TEX_PLATFORM_FX9860G) || defined(TEX_PLATFORM_FXCG50) +#undef TEX_DEBUG +#elif !defined(TEX_DEBUG) +#define TEX_DEBUG +#endif + +//--- +// Module interface +// This TeX module does not have its own primitive rendering methods; +// instead it relies on a few user-provided functions. +// This gives a certain amount on freedom on rendering. For instance, you +// can render a formula in a scrollable sub-rectangle of the screen by +// having pixel() and text() translate coordinates and check bounds. +//--- + +/* Opaque declaration - see for the details */ +struct TeX_Flow; + +/* TeX_intf_pixel() - Set a single pixel + This function configures the pixel-rendering procedure used by the module. + The argument should expect three parameters: + @x Horizontal position of the pixel (left --> right) + @y Vertical position of the pixel (top --> bottom) + @color Requested color */ +void TeX_intf_pixel(void (*draw_pixel)(int x, int y, int color)); + +/* TeX_intf_line() - Draw a line + This function sets the line drawing procedure of the module. The argument + should take give parameters: + @x1 @y1 Location of the first endpoint of the line + @x2 @y2 Location of the second endpoint of the line + @color Requested color */ +void TeX_intf_line(void (*draw_line)(int x1, int y1, int x2, int y2, + int color)); + +/* TeX_intf_size() - Get the dimensions of a string + This function configures the procedure used by the module to compute the + size of a string node. The argument should expect three parameters: + @str String whose dimensions are requested + @width Pointer to width value (must be updated by function) + @height Pointer to height value (must be updated by function) */ +void TeX_intf_size(void (*text_size)(char const *str, int *width,int *height)); + +/* TeX_intf_text() - Draw variable-width text + This function configures the text-rendering procedure used by the module. + Four arguments will be passed: + @x x coordinate of the left-side of the bounding box (included) + @y y coordinate of the top-size of the bounding box (included) + @str String to render + @color Requested color for the rendered pixels + The rendering function must honor the size estimates provided by the + procedure assigned to TeX_intf_size() and never draw pixel outside the + announced bounding box. */ +void TeX_intf_text(void (*draw_text)(char const *str, int x, int y,int color)); + +//--- +// Parsing +// The following functions create recursive TeX_Flow objects that +// represent the structure of the formulae. Once parsed, TeX_Node objects +// are rendered very quickly. +//--- + +/* TeX_parse() - Parse a TeX formula + @formula TeX formula to parse + Returns a dynamically-allocated TeX_Flow object that can be used in further + calls to TeX_draw() and must be freed by a call to TeX_free(). */ +struct TeX_Flow *TeX_parse(char const *formula); + +/* TeX_free() - Free an allocated TeX formula + Freed formulas become dangling and must not be used in any further call. + @formula Formula to free, assumed allocated by TeX_parse() */ +void TeX_free(struct TeX_Flow *formula); + +//--- +// Rendering +// These functions are used to draw formulae on the screen. As doing +// parsing several times is inefficient, it is advised to use TeX_draw() +// as much as possible by reusing the same TeX_Flow object. +//--- + +/* TeX_draw() - Render a parsed formula + @formula Formula to render + @x x coordinate of the left-side of the bounding box (included) + @y y coordinate of the top-size of the bounding box (included) + @color Requested color for the rendered pixels */ +void TeX_draw(struct TeX_Flow *formula, int x, int y, int color); + +/* TeX_interpret() - Parse and render, in sequence + This function parses the provided formula, renders it and then frees it. + Only use it if you're going to render the formula only once or if you're + very short on memory. + @formula Formula to render (textual form) + @x x coordinate of the left-side of the bounding box (included) + @y y coordinate of the top-size of the bounding box (included) + @color Requested color for the rendered pixels */ +void TeX_interpret(char const *formula, int x, int y, int color); + +#endif /* TEX_H */ diff --git a/include/TeX/defs.h b/include/TeX/defs.h new file mode 100644 index 0000000..3fdbe3b --- /dev/null +++ b/include/TeX/defs.h @@ -0,0 +1,23 @@ +//--- +// defs: Just generic definitions +//--- + +#ifndef DEFS_H +#define DEFS_H + +/* uint type for bit fields */ +typedef unsigned int uint; + +/* min(), max() - But don't duplicate side-effects */ +#define min(a, b) ({ \ + __auto_type _a = (a); \ + __auto_type _b = (b); \ + _a < _b ? _a : _b; \ +}) +#define max(a, b) ({ \ + __auto_type _a = (a); \ + __auto_type _b = (b); \ + _a > _b ? _a : _b; \ +}) + +#endif /* DEFS_H */ diff --git a/include/TeX/parser.h b/include/TeX/parser.h new file mode 100644 index 0000000..acb5515 --- /dev/null +++ b/include/TeX/parser.h @@ -0,0 +1,44 @@ +//--- +// parser: interface to the TeX parser +//--- + +#ifndef TEX_PARSER +#define TEX_PARSER + +#include +#include + +/* parse_start(): Configure parser to run on a string + @str Input data, must be NULL-terminated */ +void parse_start(const char *str); + +/* parse(): Parse into a TeX flow + Uses input data set by parse_start(). Returns a flow object on success (free + with TeX_free()), NULL on error (generally syntax issues). + TODO: Fine-grained error detection? */ +struct TeX_Flow *parse(void); + +#ifdef TEX_DEBUG + +/* TeX_debug_lex(): Display the result of lexing + Makes a lexical analysis of the given formula and prints the stream of + tokens to standard output. */ +void TeX_debug_lex(const char *formula); + +/* TeX_debug_node(): Recursively display the structure of a node + Displays the nature and dimensions of the given node and its children, + making recursive calls to TeX_debug_flow(). + @node Displayed node + @indent Indent level, in spaces (goes up by groups of 4) */ +void TeX_debug_node(struct TeX_Node *node, int indent); + +/* TeX_debug_flow(): Recursively display the structure of a flow + Displays the dimensions of the given flow and its contents, making recursive + calls to TeX_debug_node(). + @flow Displayed flow + @indent Indent level, in spaces (goes up by groups of 4) */ +void TeX_debug_flow(struct TeX_Flow *flow, int indent); + +#endif /* TEX_DEBUG */ + +#endif /* TEX_PARSER */ diff --git a/include/TeX/render.h b/include/TeX/render.h new file mode 100644 index 0000000..5baf9b0 --- /dev/null +++ b/include/TeX/render.h @@ -0,0 +1,23 @@ +//--- +// render: Rendering functions +//--- + +#ifndef TEX_RENDER +#define TEX_RENDER + +/* Rendering-related interface functions */ +extern void (*TeX_pixel)(int x, int y, int color); +extern void (*TeX_line)(int x1, int y1, int x2, int y2, int color); +extern void (*TeX_size)(char const * str, int *width, int *height); +extern void (*TeX_text)(char const * str, int x, int y, int color); + +/* render_node() - Recursively render a node + This function all nodes types (including text). The node's size must have + been computed first. */ +void render_node(struct TeX_Node const * node, int x, int y, int color); + +/* render_flow() - Render a flow and all its components + This function arranges and renders all horizontal objects in a flow. */ +void render_flow(struct TeX_Flow const * flow, int x, int y, int color); + +#endif /* TEX_RENDER */ diff --git a/include/TeX/structure.h b/include/TeX/structure.h new file mode 100644 index 0000000..d8ddaf1 --- /dev/null +++ b/include/TeX/structure.h @@ -0,0 +1,153 @@ +//--- +// structure: Recursive node/flow data structure +//--- + +#ifndef TEX_STRUCTURE +#define TEX_STRUCTURE + +#include +#include +#include + +/* Maximum number of command arguments allowed by the library (each node has + exactly this number of children; memory usage is better if this is small) */ +#define TEX_MAX_CHILDREN 2 + +/* A special class number for nodes that contain plain text. Class numbers from + 1 onwards are used by TeX_Class. See also the class array in nodes.c. */ +#define TEX_NODECLASS_TEXT 0 + +//--- +// Data structures +// The structure mixes linked lists (text flow along a line, TeX_Flow) and +// trees (arguments to commands, TeX_Node) in a mutually-recursive way. +//--- + +/* TeX_Flow + This object represents a horizontal line of nodes following the flow of the + text; each node can be a plain string or a functional node. All share the + same base line when rendered. */ +struct TeX_Flow +{ + /* Pointer to first and last elements of the line */ + struct TeX_Node *first; + struct TeX_Node *last; + + uint16_t width; + uint16_t height; + uint16_t line; + +} __attribute__((packed, aligned(sizeof (void *)))); + +/* TeX_Node + Here's the main deal. TeX_Flow objects handle the flow and TeX_Node objects + handle the content and its structure. */ +struct TeX_Node +{ + /* Pointer to next element in the flow */ + struct TeX_Node *next; + + union { + /* Plain text content encoded as UTF-8 */ + uint8_t *text; + /* Functional arguments */ + struct TeX_Flow *args[TEX_MAX_CHILDREN]; + }; + + /* Invalid or hidden node */ + uint hidden :1; + /* Node class identifier */ + uint type :7; + /* Class variant or fixed argument */ + uint subtype :8; + + /* Size and placement */ + uint16_t width; + uint16_t height; + uint16_t line; + + /* Location withing flow, as x and baseline displacement */ + uint16_t x; + int16_t l; + +} __attribute__((packed, aligned(sizeof (void *)))); + +//--- +// Class nodes +// These are the nodes that have arguments, and specific size-calculation +// and rendering methods. Most interesting nodes (fractions, sums, +// matrices...) fall into this category. +//--- + +/* TeX_Class + A class of nodes (ie. "\name{arg}{arg}..."): function name, size-calculation + method, rendering method. */ +struct TeX_Class +{ + /* Most of the time this name appears in the formula using the "\name" + notation, but some classes (superscript, parentheses, matrices...) + have special syntax and their names are hardcoded. To avoid + conflicts, built-in class names start with a backslash. */ + const char *name; + + /* size() + This function must calculate the width, height and base line of the + node and store the results in the structure fields. It is allowed to + access the .width, .height and .line fields of its children because + they will have had their size calculated beforehand. + + This TeX module provides classes with a primitive function that + calculates the size of raw strings: + void text_size(const char *str, int *width, int *height); + + @node A node of the described class, for size calculation */ + void (*size)(struct TeX_Node *node); + + /* render() + This function must render the given node at the provided (x, y) + coordinates. The (x, y) point is the top-left corner of the bounding + box of the expression and is inside the box (ie. it can be drawn + to). This function must honor the size estimates provided by + calculate_size() and not draw outside the bounding box. + + This TeX module provides two primitive functions for rendering: + void pixel(int x, int y, int color); + void text(const char *str, int x, int y, int color); + + @node A node of the described class, for rendering + @x Horizontal coordinate of the requested rendering position + @y Vertical coordinate of the requested rendering position + @color Requested rendering color */ + void (*render)(struct TeX_Node const * node, int x, int y, int color); +}; + +/* class_find() - Find a class using a command name + @name Command name + Returns a positive class id representing the requested TeX_Class object (if + a class with this name is found in the table, a negative number otherwise. + This function never returns 0, which is reserved for TEX_NODECLASS_TEXT. */ +int class_find(const char *name); + +/* Class table. This is an array that can be indexed by class_id - 1 where + class_id is an id returned by class_find(). It is terminated by a class + with a NULL command name. Be careful because the first element in this array + has class id 1. */ +extern struct TeX_Class TeX_table[]; + +//--- +// Recursive size computation +//--- + +/* size_node() - Calculate the size of node + This function handles all node classes (including text) and computes the + size of the children in advance, as required by the class' calculate_size() + method. Modifies the node structure. */ +void size_node(struct TeX_Node *node); + +/* size_flow() - Calculate the size of a flow + This function calculates the size taken by nodes sharing the same baseline. + It heeds for special size and alignment exceptions such as parenthesis-type + characters and subscripts/superscripts. Modifies the flow structure. */ +void size_flow(struct TeX_Flow *flow); + +#endif /* TEX_STRUCTURE */ diff --git a/src/TeX.c b/src/TeX.c new file mode 100644 index 0000000..c74e169 --- /dev/null +++ b/src/TeX.c @@ -0,0 +1,147 @@ +#include +#include +#include +#include +#include +#include +#include + +//--- +// Interface functions +//--- + +/* Rendering-related interface functions */ +void (*TeX_pixel)(int x, int y, int color) = NULL; +void (*TeX_line)(int x1, int y1, int x2, int y2, int color) = NULL; +void (*TeX_size)(char const *str, int *width, int *height) = NULL; +void (*TeX_text)(char const *str, int x, int y, int color) = NULL; + +/* TeX_intf_pixel() - Set a single pixel */ +void TeX_intf_pixel(void (*draw_pixel)(int x, int y, int color)) +{ + TeX_pixel = draw_pixel; +} + +/* TeX_intf_line() - Draw a line */ +void TeX_intf_line(void (*draw_line)(int x1, int y1, int x2, int y2, + int color)) +{ + TeX_line = draw_line; +} + +/* TeX_intf_size() - Get the dimensions of a string */ +void TeX_intf_size(void (*text_size)(const char *str, int *width,int *height)) +{ + TeX_size = text_size; +} + +/* TeX_intf_text() - Draw variable-width text */ +void TeX_intf_text(void (*draw_text)(const char *str, int x, int y,int color)) +{ + TeX_text = draw_text; +} + +//--- +// Object management +//--- + +/* TeX_free_node() - Free TeX_Node objects + @node Node to free. Its parameters will also be freed */ +static void TeX_free_node(struct TeX_Node *node) +{ + /* Text nodes: free the allocated string */ + if(node->type == TEX_NODECLASS_TEXT) free(node->text); + + /* Class nodes: recursively free children */ + else for(int i = 0; i < TEX_MAX_CHILDREN; i++) + { + if(node->args[i]) TeX_free(node->args[i]); + } + + free(node); +} + +/* TeX_free() - Free an allocated TeX formula */ +void TeX_free(struct TeX_Flow *flow) +{ + if(!flow) return; + + struct TeX_Node *node, *next = NULL; + + for(node = flow->first; node; node = next) + { + next = node->next; + TeX_free_node(node); + } + + free(flow); +} + +//--- +// Node operations: size computation and rendering +//--- + +/* size_node() - Calculate the size of node */ +void size_node(struct TeX_Node *node) +{ + /* Text nodes */ + if(node->type == TEX_NODECLASS_TEXT) + { + int w, h; + TeX_size((char const *)node->text, &w, &h); + + node->width = w; + node->height = h; + node->line = (h+1) >> 1; + + return; + } + + /* For other classes, first compute the size of children */ + for(int i = 0; i < TEX_MAX_CHILDREN && node->args[i]; i++) + size_flow(node->args[i]); + + /* Then use the class' special size computation function */ + if(TeX_table[node->type - 1].size) + TeX_table[node->type - 1].size(node); +} + +/* render_node() - Recursively render a node */ +void render_node(struct TeX_Node const * node, int x, int y, int color) +{ + /* Don't render hidden or invalid elements */ + if(node->hidden) return; + + if(node->type == TEX_NODECLASS_TEXT) + { + TeX_text((void *)node->text, x, y, color); + return; + } + + /* For non trivial classes, use the class' special function */ + if(TeX_table[node->type - 1].render) + TeX_table[node->type - 1].render(node, x, y, color); +} + +//--- +// Module functions +//--- + +/* TeX_parse() - Parse a TeX formula */ +struct TeX_Flow *TeX_parse(const char *formula) +{ + struct TeX_Flow *flow; + + parse_start(formula); + flow = parse(); + if(!flow) return NULL; + + size_flow(flow); + return flow; +} + +/* TeX_draw() - Render a parsed formula */ +void TeX_draw(struct TeX_Flow *formula, int x, int y, int color) +{ + render_flow(formula, x, y, color); +} diff --git a/src/TeX.y b/src/TeX.y new file mode 100644 index 0000000..6115950 --- /dev/null +++ b/src/TeX.y @@ -0,0 +1,589 @@ +/* TODO Notes for the calculator version. + Don't use GLR (= non-deterministic forks) + Provide , , , + To access semantic value of an object, use "-> value" + To suppress locations on-calc: #define YYLLOC_DEFAULT(Cur, Rhs, N) + (or just don't pass --locations) */ + +%{ + +#include +#include +#include + +#include +#include + +/* yylex() - The lexer, as usual */ +int yylex(void); +/* yyerror() - The error function */ +void yyerror(const char *); + +static struct TeX_Flow *result = NULL; + +//--- +// Node allocation functions +//--- + +/* mkflow_cons() - Add a new node at the right edge of a flow + @flow Updated flow + @node New node to add + Returns the new flow, which my be a different pointer if [flow] is NULL. */ +static struct TeX_Flow *mkflow_cons(struct TeX_Flow*flow, struct TeX_Node*node) +{ + if(!node) return flow; + + if(!flow) + { + flow = calloc(1, sizeof *flow); + if(!flow) { yyerror("[[out of memory]]"); return NULL; } + + flow->first = flow->last = node; + return flow; + } + + flow->last->next = node; + flow->last = node; + return flow; +} + +/* mknode_text() - Create a new text container node + @str 8-byte contained string + Returns a new node with an internal format for the string. The original + string may disappear. */ +static struct TeX_Node *mknode_text(const char *str) +{ + struct TeX_Node *node = calloc(1, sizeof *node); + if(!node) { yyerror("[[out of memory]]"); return NULL; } + + /* TODO: Don't use strdup(); use a format conversion instead */ + node->text = malloc(strlen(str) + 1); + if(!node->text) { + yyerror("[[out of memory]]"); free(node); return NULL; } + strcpy((void *)node->text, str); + node->type = TEX_NODECLASS_TEXT; + + return node; +} + +/* mknode_command() - Create a new command node + @name Command name + This function looks up the command name in the class table and returns NULL + if the command is not available. Otherwise it returns a new node without + arguments for this command. */ +static struct TeX_Node *mknode_command(const char *name) +{ + /* Get the class id from the class table */ + int type = class_find(name); + if(type < 0) { yyerror("[[ unknown command ]]"); return NULL; } + + struct TeX_Node *node = calloc(1, sizeof *node); + if(!node) { yyerror("[[out of memory]]"); return NULL; } + + node->type = type; + node->next = NULL; + return node; +} + +/* mknode_arg() - Add an argument to a command node + @node Node to add an argument to + @arg Argument flow + Always returns node. If node is not a plain text node and has at least one + free argument slot, then arg is added at the first free slot. Otherwise, + arg is freed and node is returned unchanged. */ +static struct TeX_Node *mknode_arg(struct TeX_Node *node, struct TeX_Flow *arg) +{ + if(!node || !arg) return node; + + /* Drop the argument if it's for a plain text node */ + if(node->type == TEX_NODECLASS_TEXT) + { + yyerror("[[dropping argument of plain text node]]"); + TeX_free(arg); + return node; + } + + /* Otherwise look up a free slot in the argument array of node */ + int i = 0; + while(i < TEX_MAX_CHILDREN && node->args[i]) i++; + + if(i >= TEX_MAX_CHILDREN) + { + yyerror("[[too much arguments for node]]"); + TeX_free(arg); + } + else node->args[i] = arg; + + return node; +} + +/* mknode_absarg() - Add an absorbed argument to a command node + @node Command node + @arg Text argument + For special cases such as left/right, sets the subtype, otherwise creates + a text flow as usual. */ +static struct TeX_Node *mknode_absarg(struct TeX_Node *node, char const *text) +{ + char const *class = ""; + if(node->type != TEX_NODECLASS_TEXT) + class = TeX_table[node->type - 1].name; + + if(!strcmp(class, "left") || !strcmp(class, "right")) + { + node->subtype = text[0]; + return node; + } + + return mknode_arg(node, mkflow_cons(NULL, mknode_text(text))); +} + +/* mknode_f() - Make a function node with a flow argument + @name Command name; intended for built-in nodes only + @flow Flow argument + Returns a node for command [name] with as argument [flow]. */ +static struct TeX_Node *mknode_f(const char *name, struct TeX_Flow *flow) +{ + struct TeX_Node *node = mknode_command(name); + return mknode_arg(node, flow); +} + +/* mknode_t() - Make a function node with a text argument */ +#define mknode_t(name, text) \ + mknode_f((name), mkflow_cons(NULL, mknode_text((text)))) + +%} + +%define api.value.type union + +%token TEXT +%token COMMAND +%token COMMAND_ABS +%left '^' '_' + +%type flow +%type node +%type node_abs + +%% + +main: + flow { result = $1; } + +flow: + %empty { $$ = NULL; } + | flow node { $$ = mkflow_cons($1, $2); } + +node: + TEXT { $$ = mknode_text($1); } + | COMMAND { $$ = mknode_command($1); } + | node_abs { $$ = $1; } + | node '{' flow '}' { $$ = mknode_arg($1, $3); } + /* Special shortcuts for the superscript and subscript classes */ + | '^' TEXT { $$ = mknode_t("\\sup", $2); } + | '_' TEXT { $$ = mknode_t("\\sub", $2); } + | '^' '{' flow '}' { $$ = mknode_f("\\sup", $3); } + | '_' '{' flow '}' { $$ = mknode_f("\\sub", $3); } + +/* TODO: shift/reduce for [COMMAND_ABS TEXT] - could split in two nodes */ + +node_abs: + COMMAND_ABS { $$ = mknode_command($1); } + | node_abs TEXT { $$ = mknode_absarg($1, $2); } +%% + +//--- +// The lexer +// +// The lexical analysis is actually the subtle part in this program, +// because we want to preserve text sections without cutting them to avoid +// merging string tokens later on. +// +// The program below is a lexer with some hybrid parser features that +// accomplishes this task. +// - It has a lookahead character and never retracts. +// - It has a notion of context (notably single-character mode). +// This machinery, however, can only be edited by hand. +// +// The good side of this is that it's much more efficient because we don't +// break sections without commands, so there are less allocations and less +// tokens to parse. +//--- + +/* Character source (input formula) */ +static const char *lex; + +/* Accumulator. This buffer contains text that will be emitted in the next + token, more precisely everything between the start of the token and [lex] + (the [lex] pointer will move forward during lexing), but with escape + characters decoded. + + Note that this buffer is static *and* unique, so every token will make its + way through the accumulator. As a consequence, we must apply a parsing rule + that copies the accumulated string every time we flush the buffer. If a + parsing rule has several parameters with data in the accumulator, then all + except the last will have their data overridden before the semantic rule is + executed. */ +static char acc_buffer[TEX_LEXER_BUFSIZE]; + +/* Position of the next free character in [acc_buffer] */ +static char *acc; + +/* enum state - Lexer automaton states. + The automaton state represents what has been discovered at the previous + step; the state is changed to store instructions for the next character + input round. Often, when a character is read, previously-lexed input is + emitted and the character is stored in the accumulator. Possibly the state + is changed. */ +static enum { + /* Reached end-of-file (continuously emits $end tokens) */ + eof = 0, + /* When reading text sections to be displayed on the screen, possibly + with escapes - this is the part we don't want to cut. */ + text, + /* When reading a command name after a '\' */ + command, + /* The following states are transitioned into when their characters are + read from input. The associated token will only be emitted in the + next character input round. */ + superscript = '^', + subscript = '_', + lbrace = '{', + rbrace = '}', +} state = text; + +/* Single-character mode. When a command name, '^' or '_' is not followed by a + '{', the argument is taken to be the next input character. This mode alters + the behavior of the [text] state (mainly) to emit a token at the next + character instead of storing data in the accumulator. */ +int single; +/* Lookahead symbol. The combination of the lookahead character with the + delayed-emission described in [enum state] gives this lexer two characters + to make decisions. For example, when lexing "ab^2", at the third character + input round: + * '^' is read from the input + * "ab" is released as a TEXT token and the accumulator is emptied + * '2' is seen as lookahead and single-character mode is activated */ +static int la; + +/* forward() - Maybe return + Returns the value of @expr if it's nonnegative; does nothing otherwise. */ +#define forward(expr) do { \ + int v = expr; \ + if(v >= 0) return v; \ +} while(0) + +/* lexer_init() - TODO: Document lexer_init() */ +void lexer_init(const char *formula) +{ + acc = acc_buffer; + + /* When the formula is empty, don't let the lexer read a lookahead! */ + if(!formula || !formula[0]) + { + lex = NULL; + state = eof; + la = 0; + single = 0; + return; + } + + lex = formula + 1; + state = text; + la = formula[0]; + single = 0; +} + +/* release() - Release the lexer buffer as a textual token + This function returns produces a text token of the requested types using the + contents of the lexer buffer, but only if the buffer is not empty. Thus the + call must be wrapped into forward() and not return if there is a fallback + action. + @token Requested token type + Returns a nonnegative token number if the buffer is not empty, or -1. */ +static int release(int token) +{ + if(acc == acc_buffer) return -1; + + *acc++ = 0; + acc = acc_buffer; + + /* After all we don't need to switch on token */ + /* WARN: may be fragile, look for appropriate flags */ + yylval.TEXT = yylval.COMMAND = yylval.COMMAND_ABS = acc_buffer; + return token; +} + +/* accumulate() - Accumulate characters in the lexer buffer + Adds a new character @c to the lexer buffer. If the buffer is full or if + single-character mode is active, emits a token and empties the buffer. For + this return to work, the call to accumulate() must be wrapped in forward(). + @c Character to add (1 byte) + Returns a nonnegative token number if one is emitted, -1 otherwise. */ +static int accumulate(int c) +{ + *acc++ = c; + + /* Send a token if the buffer is full or single-character mode is on */ + if(acc >= acc_buffer + TEX_LEXER_BUFSIZE - 1 || single) + { + single = 0; + + switch(state) + { + case text: return release(TEXT); + case command: return release(COMMAND); + default: break; + } + } + + /* Continue lexing for now */ + return -1; +} + +/* acccmp() - String comparison with the accumulator + Like strcmp(), but uses the non-NUL-terminated accumulator as one input. */ +static int acccmp(const char *str) +{ + return strncmp(acc_buffer, str, acc - acc_buffer); +} + +/* lexer_text() - Execute a step of lexing from the text state + + In text state, we are accumulating characters as long as possible without + releasing tokens. Longer chunks of text render faster on monochrome + calculators and need less memory. + + This mode is exited whenever a metacharacter, that is '{', '}', '^' or '_'. + The backslash character '\' is treated with more care because escaped + metacharacters such as '\{' still count as text and don't need us to release + the accumulator. + + Returns a token ID if one is emitted, -1 otherwise. */ +static int lexer_text(void) +{ + /* Feed lexer: this is safe thanks because I heed for c = EOF */ + int c = la; + if(!c) { state = eof; return release(TEXT); } + la = *lex++; + + /* Escapes and command names */ + if(c == '\\') + { + /* Command name: release current buffer and move */ + if(la >= 'a' && la <= 'z') + { + state = command; + return release(TEXT); + } + + /* Escaped character: accumulate lookahead and feed lexer. + Feeding is safe because current lookahead is not EOF */ + if(strchr("\\{}^_", la)) + { + c = la; + la = *lex++; + + /* Intentional fall-through */ + } + /* TODO: Emit a warning in an "else" clause? */ + + return accumulate(c); + } + + /* Opening and closing braces are always syntactic elements */ + else if(c == '{' || c == '}') + { + state = c; + return release(TEXT); + } + + /* Superscript and subscript: heed for various input modes */ + else if(c == '^' || c == '_') + { + /* In all cases, prepare to emit c at next lexing round */ + state = c; + + /* If the next character is not '{', then we don't have a {} + for the argument; enable single-character mode */ + if(la != '{') single = 1; + + /* Then emit what was already in the buffer, as text */ + return release(TEXT); + } + + /* Accumulate the current character in the buffer until it's full */ + return accumulate(c); +} + +/* lexer_command() - Execute of step of lexing from the command state + + This state is transitioned into when a '\' followed by a letter is + encountered. The lexer remains there until the end of the command, signaled + by a non-letter character, is reached. + + At this point, the lexer can either enter the text mode again and wait for a + '{' to start arguments, or enter the text mode with the single-character + argument flag so any character that comes next is treated as the argument. + + The original TeX does this conditionally, using single-character arguments + if and only if the argument does not start with a '{'. However, in this + program, because the number of arguments is not known at parse time, this + would make dumb commands such as '\alpha' gobble an unbounded amount of + arguments. So all commands use brace-only arguments, except for a designated + set. Typically this includes \left and \right which are mainly used without + braces in practice. + + Returns a token ID if one is emitted, -1 otherwise. */ +static int lexer_command(void) +{ + /* Feed lexer; this is safe because I heed for la = EOF */ + int c = la; + if(!c) { state = eof; return release(COMMAND); } + la = *lex++; + + /* In this state, c is always in the range a .. z */ + int ret = accumulate(c); + + /* Continue if next character is a command continuation */ + if(la >= 'a' && la <= 'z') return ret; + + /* Otherwise, release command name */ + state = text; + + /* Absorbing commands include "left" and "right" */ + if(!acccmp("left") || !acccmp("right")) + { + single = 1; + return release(COMMAND_ABS); + } + + return release(COMMAND); +} + +/* yylex() - The lexer + Returns the token type of the next token in the string initialized by the + last call to parse_start(). */ +int yylex(void) +{ + while(1) + { + if(state == text) forward(lexer_text()); + else if(state == command) forward(lexer_command()); + else break; + } + + /* End-of-File: give up */ + if(state == eof) return 0; + + /* Character-specific states: feed and return state number */ + int c = state; + state = text; + return c; +} + +//--- +// Parser interface +//--- + +/* parse_start(): Configure parser to run on a string */ +void parse_start(const char *str) +{ + lexer_init(str); +} + +/* parse(): Parse into a TeX flow */ +struct TeX_Flow *parse(void) +{ + int x = yyparse(); + return x ? NULL: result; +} + +#ifdef TEX_DEBUG + +#include + +void yyerror(const char *error) +{ + fprintf(stderr, "Parsing failed: %s\n", error); +} + +#else /* TEX_DEBUG */ + +void yyerror(__attribute__((unused)) const char *error) +{ +} + +#endif /* TEX_DEBUG */ + +//--- +// Debugging functions +//--- + +#ifdef TEX_DEBUG + +#define GRAY "\e[30;1m" +#define END "\e[0m" + +/* TeX_debug_lex(): Display the result of lexing on stdout */ +void TeX_debug_lex(const char *formula) +{ + lexer_init(formula); + int token; + + do + { + token = yylex(); + + printf("%-3d ", token); + if(strchr("{}^_", token)) printf("%c", token); + if(token == 258) printf("TEXT %s", yylval.TEXT); + if(token == 259) printf("COMMAND %s", yylval.COMMAND); + if(token == 260) printf("COMMAND_ABS %s", yylval.COMMAND_ABS); + printf("\n"); + } + while(token != 0); +} + +/* TeX_debug_node(): Recursively display the structure of a node */ +void TeX_debug_node(struct TeX_Node *node, int indent) +{ + printf("%*s", indent, ""); + if(!node) { puts("node (null)"); return; } + + if(node->type == TEX_NODECLASS_TEXT) + { + printf("\"%s\"", node->text); + printf(GRAY " %dx%d,%d %+d%+d" END "\n", node->width, + node->height, node->line, node->x, node->l); + } + else + { + printf("<%s>", TeX_table[node->type - 1].name); + if(node->subtype) + printf(" subtype '%c'", node->subtype); + printf(" "GRAY "%dx%d,%d %+d%+d" END "\n", node->width, + node->height, node->line, node->x, node->l); + for(int i = 0; i < TEX_MAX_CHILDREN && node->args[i]; i++) + TeX_debug_flow(node->args[i], indent + 4); + } +} + +/* TeX_debug_flow(): Recursively display the structure of a flow */ +void TeX_debug_flow(struct TeX_Flow *flow, int indent) +{ + printf("%*s", indent, ""); + if(!flow) { puts("flow (null)"); return; } + + printf("flow " GRAY "%dx%d,%d" END "\n", flow->width, flow->height, + flow->line); + + struct TeX_Node *node = flow->first; + + while(node) + { + TeX_debug_node(node, indent + 4); + node = node->next; + } +} + +#endif /* TEX_DEBUG */ diff --git a/src/classes.c b/src/classes.c new file mode 100644 index 0000000..fcef725 --- /dev/null +++ b/src/classes.c @@ -0,0 +1,255 @@ +//--- +// classes: Node classes used for mathematical notations +//--- + +#include +#include +#include +#include +#include + + +/* Some quick macros to access parameters and their size */ +#define args ((node)->args) +#define w(n) (args[n]->width) +#define h(n) (args[n]->height) +#define l(n) (args[n]->line) + +//--- +// Fractions. +// * args: 2 (<2 invalidate, >2 ignore) +// * flow: normal +// +// Graphical parameters: +// +// TEX_LAYOUT_SPACING (inherited): +// Spacing between bar and operands +// TEX_FRACTION_BAR_THICKNESS: +// Thickness (pixels) of fraction bar +// TEX_FRACTION_BAR_MARGIN: +// Additional bar length that extends beyond the operands +// +// 1 +// ] TEX_LAYOUT_SPACING +// ####### ] TEX_FRACTION_BAR_THICKNESS +// ] TEX_LAYOUT_SPACING +// 2 +// |_| |_| +// TEX_FRACTION_BAR_MARGIN +//--- + +void frac_size(struct TeX_Node *node) +{ + /* Drop the fraction if there are less than two arguments */ + if(!args[1]) { node->hidden = 1; return; } + + int margin = TEX_FRACTION_BAR_MARGIN; + int thickness = TEX_FRACTION_BAR_THICKNESS; + int spacing = TEX_LAYOUT_SPACING; + + node->width = max(w(0), w(1)) + 2 * margin; + node->height = h(0) + h(1) + 2 * spacing + thickness; + node->line = h(0) + spacing + thickness / 2; +} + +void frac_render(struct TeX_Node const * node, int x, int y, int color) +{ + /* Fraction bar */ + for(int i = 0; i < TEX_FRACTION_BAR_THICKNESS; i++) + { + int y0 = y + h(0) + TEX_LAYOUT_SPACING + i; + TeX_line(x, y0, x + node->width - 1, y0, color); + } + + /* First argument */ + render_flow(args[0], + x + ((node->width - w(0)) >> 1), + y, + color); + + /* Second argument */ + render_flow(args[1], + x + ((node->width - w(1)) >> 1), + y + h(0) + 2 * TEX_LAYOUT_SPACING + TEX_FRACTION_BAR_THICKNESS, + color); +} + +//--- +// Left and right delimiters. +// * args: 1 (<1 invalidate, >1 ignore) +// * flow: adapt width and height to contents until matching delimiter +// +// Left and right delimiters are resizable bracket-like elements used to +// parenthesize or decorate terms. They serve a special flow purpose and +// are matched together by the size_flow() routine. +// +// Graphical parameters: +// +// TEX_LEFTRIGHT_ALIGNED: +// Forces the middle of the decoration to be aligned with the baseline +// TEX_LEFTRIGHT_SYMMETRICAL: +// Makes the size of the delimiter symmetrical around the baseline +// TEX_LEFTRIGHT_MAX_ANGLE: +// Maximum angle allowed for "<" and ">" delimiters, radians (causes +// these delimiters to grow in width if their content is tall) +// +// Default: Aligned: Aligned symmetrical: +// / x / x / x +// | - | - | - +// < y | y | y +// | --- < --- < --- +// \ z \ z | z +// | +// \. +// +// LaTeX's behavior on this is essentially centered symmetrical, but it +// seems to resize the contents to make it smoother. Not possible here. +//--- + +void leftright_size(struct TeX_Node *node) +{ + int width, height; + TeX_size("#", &width, &height); + + /* TODO: Not the same on fxcg50 */ + node->width = 3; + node->height = height; + node->line = (height+1) >> 1; +} + +void leftright_render(struct TeX_Node const * node, int x, int y, int color) +{ + int h = node->height, l = node->line; + + switch(node->subtype) + { + case '(': + TeX_pixel(x + 2, y, color); + TeX_pixel(x + 1, y + 1, color); + TeX_line(x, y + 2, x, y + h - 3, color); + TeX_pixel(x + 1, y + h - 2, color); + TeX_pixel(x + 2, y + h - 1, color); + break; + + case ')': + TeX_pixel(x, y, color); + TeX_pixel(x + 1, y + 1, color); + TeX_line(x + 2, y + 2, x + 2, y + h - 3, color); + TeX_pixel(x + 1, y + h - 2, color); + TeX_pixel(x, y + h - 1, color); + break; + + case '[': + TeX_pixel(x + 1, y, color); + TeX_pixel(x + 2, y, color); + TeX_line(x, y, x, y + h - 1, color); + TeX_pixel(x + 1, y + h - 1, color); + TeX_pixel(x + 2, y + h - 1, color); + break; + + case ']': + TeX_pixel(x, y, color); + TeX_pixel(x + 1, y, color); + TeX_line(x + 2, y, x + 2, y + h - 1, color); + TeX_pixel(x, y + h - 1, color); + TeX_pixel(x + 1, y + h - 1, color); + break; + + case '{': + TeX_pixel(x + 2, y, color); + TeX_line(x + 1, y + 1, x + 1, y + l - 1, color); + TeX_pixel(x, y + l, color); + TeX_line(x + 1, y + l + 1, x + 1, y + h - 2, color); + TeX_pixel(x + 2, y + h - 1, color); + break; + + case '}': + TeX_pixel(x, y, color); + TeX_line(x + 1, y + 1, x + 1, y + l - 1, color); + TeX_pixel(x + 2, y + l, color); + TeX_line(x + 1, y + l + 1, x + 1, y + h - 2, color); + TeX_pixel(x, y + h - 1, color); + break; + } +} + +//--- +// Superscripts and subscripts. +// * args: 1 (<1 invalidate, >1 ignore) +// * flow: elevated or lowered to extend the previous node +// +// Superscript and subscripts are very common elements. The class itself +// does nothing but name them and render its argument. The only special +// thing about them is their behavior in a flow, which depends on the +// context and is handled by flow functions in [TeX.c]. +// +// Graphical parameters: +// +// TEX_SUPERSCRIPT_DEPTH: +// Distance between bottom of superscript and top of base +// TEX_SUBSCRIPT_ELEVATION: +// Distance between top of subscript and bottom of base +// +// +-----+ +// | | +// +------+ 1 | --, +// | Base | | | TEX_SUPERSCRIPT_DEPTH +// | +-----+ --' +// +------+ +// +// +------+ +// | +-----+ --, +// | Base | | | TEX_SUBSCRIPT_ELEVATION +// +------| 1 | --' +// | | +// +-----+ +//--- + +void supsubscript_size(struct TeX_Node *node) +{ + /* Invalidate node if no argument is provided */ + if(!args[0]) { node->hidden = 1; return; } + + node->width = w(0); + node->height = h(0); + node->line = l(0); +} + +void supsubscript_render(struct TeX_Node const * node, int x, int y, + int color) +{ + render_flow(args[0], x, y, color); +} + +//--- +// The class table and lookup functions +//--- + +struct TeX_Class TeX_table[] = { + /* Text is omitted from the table and has ID 0 */ + + /* Fractions */ + { "frac", frac_size, frac_render }, + /* Size-aware opening and closing elements */ + { "left", leftright_size, leftright_render }, + { "right", leftright_size, leftright_render }, + /* Superscripts and subscripts */ + { "\\sup", supsubscript_size, supsubscript_render }, + { "\\sub", supsubscript_size, supsubscript_render }, + /* Integral */ + { "int", NULL, NULL }, + /* NULL terminator */ + { NULL, NULL, NULL }, +}; + +/* class_find() - Find a class using a command name */ +int class_find(const char *name) +{ + /* TODO: Do a binary search instead of a linear one */ + for(int i = 0; TeX_table[i].name; i++) + { + if(!strcmp(TeX_table[i].name, name)) return i + 1; + } + + return -1; +} diff --git a/src/flow.c b/src/flow.c new file mode 100644 index 0000000..c954e14 --- /dev/null +++ b/src/flow.c @@ -0,0 +1,314 @@ +#include +#include +#include +#include + +//--- +// Chunks to place subscripts and superscripts +//--- + +#define TEX_CHUNK_INLINE 0 +#define TEX_CHUNK_DISPLAY 1 +#define TEX_CHUNK_SLANTED 2 + +/* struct chunk: Chunk of nodes positioned relative to each other + The notion of "chunk" comprises a base with a subscript and a superscript. + Together they can be arranged in three different ways: + + * TEX_CHUNK_INLINE: + Normal inline setting, subscript and superscript are placed to the right + of the base and moved below and above the baseline. + * TEX_CHUNK_DISPLAY: + Similar to LaTeX's display style, subscript and superscript are placed + under and above the base, and are centered horizontally. + * TEX_CHUNK_SLANTED: + Similar to inline mode, but subscript is slightly moved to the left and + superscript is slightly moved to the right. Looks good on integrals. + + The user of a chunk sets the [x] and [l] members of the [base] node, then + selects the display mode and optionally adds subscript and superscript. When + the chunk_finalize() function is called, the position of the elements is + computed and the [x] and [l] members of the [sub] and [sup] nodes, as well + as the geometry of the chunk, are set. */ +struct chunk +{ + /* Base node, baseline aligned on the flow */ + struct TeX_Node *base; + /* Subscript and superscript (might both be NULL) */ + struct TeX_Node *sub, *sup; + /* Display mode */ + int mode; + + /* Chunk geometry */ + int width; + int height; + int line; +}; + +/* chunk_make(): Make a new chunk for a base node */ +void chunk_make(struct chunk *chunk, struct TeX_Node *base, int mode) +{ + chunk->base = base; + chunk->mode = mode; + + chunk->sub = NULL; + chunk->sup = NULL; + + chunk->width = 0; + chunk->height = 0; + chunk->line = 0; +} + +/* chunk_reset(): Empty a chunk */ +void chunk_reset(struct chunk *chunk) +{ + memset(chunk, 0, sizeof *chunk); +} + +/* chunk_set(): Check whether a chunk is empty */ +int chunk_set(struct chunk *chunk) +{ + return chunk->base != NULL; +} + +/* chunk_set_subscript(): Add a subscript on a chunk */ +void chunk_set_subscript(struct chunk *chunk, struct TeX_Node *sub) +{ + /* Silently refuse double index */ + if(chunk->sub) return; + + chunk->sub = sub; +} + +/* chunk_set_superscript(): Add a superscript on a chunk */ +void chunk_set_superscript(struct chunk *chunk, struct TeX_Node *sup) +{ + /* Silently refuse double exponent */ + if(chunk->sup) return; + + chunk->sup = sup; +} + +/* chunk_compute_inline(): Compute chunk layout for inline mode */ +void chunk_compute_inline(struct chunk *chunk) +{ + struct TeX_Node *base = chunk->base; + struct TeX_Node *sup = chunk->sup; + struct TeX_Node *sub = chunk->sub; + + /* Height over and below baseline */ + int over = base->line; + int under = base->height - base->line; + /* Additional width */ + int right = 0; + + if(sub) + { + int elevation = TEX_SUBSCRIPT_ELEVATION; + + sub->x = base->x + base->width + TEX_LAYOUT_SPACING; + sub->l = base->l + (base->height - base->line + sub->line - + elevation); + + under = max(under, under + sub->height - elevation); + right = max(right, sub->width); + } + if(sup) + { + int depth = TEX_SUPERSCRIPT_DEPTH; + + sup->x = base->x + base->width + TEX_LAYOUT_SPACING; + sup->l = base->l - (base->line + (sup->height - sup->line) - + depth); + + over = max(over, over + sup->height - depth); + right = max(right, sup->width); + } + + if(right > 0) right += TEX_LAYOUT_SPACING; + + chunk->width = base->width + right; + chunk->height = over + under; + chunk->line = over; +} + +/* chunk_compute_display(): Compute chunk layout for display mode */ +void chunk_compute_display(struct chunk *chunk) +{ + /* TODO: Display mode for exponents and subscripts */ + chunk_compute_inline(chunk); +} + +/* chunk_compute_slanted(): Compute chunk layout for slanted mode */ +void chunk_compute_slanted(struct chunk *chunk) +{ + /* TODO: Slanted mode for exponents and subscripts */ + chunk_compute_inline(chunk); +} + +/* chunk_compute(): Compute the layout of a chunk after it's filled */ +void chunk_compute(struct chunk *chunk) +{ + if(chunk->mode == TEX_CHUNK_INLINE) + { + chunk_compute_inline(chunk); + } + if(chunk->mode == TEX_CHUNK_DISPLAY) + { + chunk_compute_display(chunk); + } + if(chunk->mode == TEX_CHUNK_SLANTED) + { + chunk_compute_slanted(chunk); + } +} + +//--- +// Groups to compute the size of parenthesis-like elements +//--- + +struct group +{ + /* Opening parenthesis/bracket/whatever */ + struct TeX_Node *left; + + /* Maximum height above and below baseline */ + int above; + int below; +}; + +/* group_open(): Create new group initialized with a left node */ +void group_open(struct group *group, struct TeX_Node *left) +{ + group->left = left; + group->above = 0; + group->below = 0; +} + +/* group_update(): Update a group with a new node */ +void group_update(struct group *group, struct TeX_Node *node) +{ + /* Account for node displacement around baseline */ + group->above = max(node->line - node->l, group->above); + group->below = max(node->height - node->line + node->l, group->below); +} + +/* group_close(): Close a group with its right node */ +void group_close(struct group *group, struct TeX_Node *right) +{ + struct TeX_Node *left = group->left; + int above = group->above; + int below = group->below; + int line = above; + + #if TEX_LEFTRIGHT_SYMMETRICAL + above = below = max(above, below); + #endif + + #if ! TEX_LEFTRIGHT_ALIGNED + line = (above + below) >> 1; + #endif + + /* Set the height of the left and right node */ + + left->height = above + below; + left->line = line; + left->l += (line - above); + + right->height = above + below; + right->line = line; + right->l += (line - above); +} + +//--- +// Laying out flows +//--- + +static void update(struct chunk *c, int *x, int *above, int *below) +{ + if(!chunk_set(c)) return; + chunk_compute(c); + + *x += c->width + TEX_LAYOUT_SPACING; + *above = max(*above, c->line - c->base->l); + *below = max(*below, c->height - c->line + c->base->l); + + chunk_reset(c); +} + +/* size_flow(): Calculate the layout of a flow */ +void size_flow(struct TeX_Flow *flow) +{ + /* Current node and current chunk */ + struct TeX_Node *node; + struct chunk c = { NULL }; + /* Position in flow */ + int x = 0; + /* Height above baseline (excluded), and below baseline (included) */ + int above = 0; + int below = 0; + /* Nested groups and current position */ + struct group groups[TEX_LEFTRIGHT_NESTING]; + struct group *g = groups; + + for(node = flow->first; node; node = node->next) + { + char const *class = ""; + size_node(node); + group_update(g, node); + + if(node->type != TEX_NODECLASS_TEXT) + class = TeX_table[node->type - 1].name; + + if(!strcmp(class, "\\sup")) + { + chunk_set_superscript(&c, node); + continue; + } + if(!strcmp(class, "\\sub")) + { + chunk_set_subscript(&c, node); + continue; + } + if(!strcmp(class, "left") && g-groups < TEX_LEFTRIGHT_NESTING) + { + g++; + group_open(g, node); + group_update(g, node); + } + if(!strcmp(class, "right") && g - groups > 0) + { + group_close(g, node); + g--; + } + + /* Leave the last chunk and make a new one */ + update(&c, &x, &above, &below); + + node->x = x; + chunk_make(&c, node, TEX_CHUNK_INLINE); + } + + /* Finish the last chunk */ + update(&c, &x, &above, &below); + + flow->height = above + below; + flow->line = above; + flow->width = max(x - TEX_LAYOUT_SPACING, 0); +} + +//--- +// Rendering flows +//--- + +/* render_flow() - Render a flow and all its components */ +void render_flow(struct TeX_Flow const * flow, int x, int y, int color) +{ + struct TeX_Node const * node; + + for(node = flow->first; node; node = node->next) + { + render_node(node, x + node->x, y + flow->line - node->line + + node->l, color); + } +} diff --git a/src/platform/cli.c b/src/platform/cli.c new file mode 100644 index 0000000..07be0dd --- /dev/null +++ b/src/platform/cli.c @@ -0,0 +1,112 @@ +//--- +// CLI interface: for parsing and computation tests on computer +//--- + +#ifdef TEX_PLATFORM_CLI + +#include +#include +#include + +#include +#include + +//--- +// Interface functions +//--- + +/* dpixel() - Set a single pixel */ +void dpixel(int x, int y, int color) +{ +} + +/* dline() - Draw a line */ +void dline(int x1, int y1, int x2, int y2, int color) +{ +} + +/* dsize() - Get the dimensions of a string */ +void dsize(char const *str, int *width, int *height) +{ + /* Use fx9860g size standards */ + *width = 6 * strlen(str) - 1; + *height = 7; +} + +/* dtext() - Draw variable-width text */ +void dtext(char const *str, int x, int y, int color) +{ +} + + +void test_auto(void) +{ +/* printf("\e[36m<><><>\e[0m \e[1mLexing\e[0m\n\n"); + + debug_lex("\\frac{1}{2}vt^2 = \\int_a^{b} f(x) dx"); + printf("\n"); + debug_lex("\\left(\\frac{1}{2}\\right)^{\\frac{n(n+1)}{2}}"); + printf("\n"); + + printf("\e[36m<><><>\e[0m \e[1mParsing\e[0m\n\n"); */ + + struct TeX_Flow *flow = NULL; + +/* flow = TeX_parse("\\frac{1}{2}vt^2 = \\int_a^{b} f(x) dx"); + if(!flow) puts("parsing error!"); + else TeX_debug_flow(flow, 0); + TeX_free(flow); + printf("\n"); + + flow = TeX_parse("\\left(\\frac{1}{2}\\right)^{\\frac{n(n+1)}{2}}"); + if(!flow) puts("parsing error!"); + else TeX_debug_flow(flow, 0); + TeX_free(flow); + printf("\n"); */ + + flow = TeX_parse("\\frac{1}{\\left(\\frac{12}{27}\\right)^2}"); + if(!flow) puts("parsing error!"); + else + { + size_flow(flow); + TeX_debug_flow(flow, 0); + } + TeX_free(flow); + printf("\n"); +} + +int main(void) +{ + /* Set interface functions */ + TeX_intf_pixel(dpixel); + TeX_intf_line(dline); + TeX_intf_size(dsize); + TeX_intf_text(dtext); + + char formula[80]; + printf("Input a string, or leave empty for auto tests:\n> "); + fgets(formula, 80, stdin); + + /* Get out the NL */ + formula[strlen(formula) - 1] = 0; + + if(!formula[0]) + { + test_auto(); + return 0; + } + + printf("\n\e[36m<><><>\e[0m \e[1mLexing\e[0m\n\n"); + TeX_debug_lex(formula); + + printf("\n\e[36m<><><>\e[0m \e[1mParsing\e[0m\n\n"); + struct TeX_Flow *flow = TeX_parse(formula); + if(!flow) puts("parsing error!"); + else TeX_debug_flow(flow, 0); + TeX_free(flow); + printf("\n"); + + return 0; +} + +#endif /* TEX_PLATFORM_CLI */ diff --git a/src/platform/sdl2.c b/src/platform/sdl2.c new file mode 100644 index 0000000..d95894a --- /dev/null +++ b/src/platform/sdl2.c @@ -0,0 +1,143 @@ +//--- +// SDL interface: for rendering tests on computer +//--- + +#ifdef TEX_PLATFORM_SDL + +#include +#include +#include +#include + +/* Rendering window and renderer */ +static SDL_Window *w = NULL; +static SDL_Renderer *r = NULL; + +//--- +// Interface functions +//--- + +/* intf_pixel() - Set a single pixel */ +void intf_pixel(int x, int y, int color) +{ + int R = color >> 16; + int G = (color >> 8) & 0xff; + int B = color & 0xff; + + SDL_SetRenderDrawColor(r, R, G, B, 255); + SDL_RenderDrawPoint(r, x, y); +} + +/* intf_line() - Draw a line */ +void intf_line(int x1, int y1, int x2, int y2, int color) +{ + int R = color >> 16; + int G = (color >> 8) & 0xff; + int B = color & 0xff; + + SDL_SetRenderDrawColor(r, R, G, B, 255); + SDL_RenderDrawLine(r, x1, y1, x2, y2); +} + +/* intf_size() - Get the dimensions of a string */ +void intf_size(char const *str, int *width, int *height) +{ + *width = 6 * strlen(str) - 1; + *height = 7; +} + +/* intf_text() - Draw variable-width text */ +void intf_text(char const *str, int x, int y, int color) +{ + int R = color >> 16; + int G = (color >> 8) & 0xff; + int B = color & 0xff; + SDL_SetRenderDrawColor(r, R, G, B, 255); + + SDL_Rect rect = { .x = x, .y = y }; + intf_size(str, &rect.w, &rect.h); + SDL_RenderDrawRect(r, &rect); +} + +//--- +// Initialization and finalization +//--- + +__attribute__((constructor)) +static void init(void) +{ + if(SDL_Init(SDL_INIT_VIDEO) < 0) + { + fprintf(stderr, "error: cannot initialize SDL backend: %s\n", + SDL_GetError()); + exit(1); + } + + int c = SDL_WINDOWPOS_CENTERED; + w = SDL_CreateWindow("2D rendering", c, c, 256, 128, 0); + if(!w) + { + fprintf(stderr, "error: cannot create SDL window: %s\n", + SDL_GetError()); + exit(1); + } + + r = SDL_CreateRenderer(w, -1, SDL_RENDERER_ACCELERATED); + if(!r) + { + fprintf(stderr, "error: cannot create SDL renderer: %s\n", + SDL_GetError()); + exit(1); + } + + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); + + /* Make the window visible */ + SDL_RenderPresent(r); + + /* Set interface functions */ + TeX_intf_pixel(intf_pixel); + TeX_intf_line(intf_line); + TeX_intf_size(intf_size); + TeX_intf_text(intf_text); +} + +__attribute__((destructor)) +static void fini(void) +{ + if(w) SDL_DestroyWindow(w); + SDL_Quit(); +} + +//--- +// Backend example +//--- + +int main(void) +{ + char const * formula = "\\frac{x_7}{" + "\\left\\{\\frac{\\frac{2}{3}}{27}\\right\\}^2" + "}"; + + struct TeX_Flow *flow = TeX_parse(formula); + if(!flow) { puts("parsing error!"); return 1; } + size_flow(flow); + TeX_debug_flow(flow, 0); + + SDL_SetRenderDrawColor(r, 0xff, 0xff, 0xff, 0xff); + SDL_RenderClear(r); + TeX_draw(flow, 10, 10, 0x000000); + SDL_RenderPresent(r); + + SDL_Event e; + while(1) + { + SDL_WaitEvent(&e); + if(e.type == SDL_QUIT) break; + } + + TeX_free(flow); + return 0; +} + +#endif /* TEX_PLATFORM_SDL */