Browse Source

initial commit: basic parsing, flow, rendering and some nodes

master
Lephenixnoir 4 months ago
commit
44549f132f
19 changed files with 2199 additions and 0 deletions
  1. 19
    0
      .gitignore
  2. 94
    0
      Makefile
  3. 11
    0
      TODO
  4. 4
    0
      config/cli.cfg
  5. 5
    0
      config/fx9860g.cfg
  6. 5
    0
      config/fxcg50.cfg
  7. 6
    0
      config/sdl2.cfg
  8. 99
    0
      configure
  9. 153
    0
      include/TeX/TeX.h
  10. 23
    0
      include/TeX/defs.h
  11. 44
    0
      include/TeX/parser.h
  12. 23
    0
      include/TeX/render.h
  13. 153
    0
      include/TeX/structure.h
  14. 147
    0
      src/TeX.c
  15. 589
    0
      src/TeX.y
  16. 255
    0
      src/classes.c
  17. 314
    0
      src/flow.c
  18. 112
    0
      src/platform/cli.c
  19. 143
    0
      src/platform/sdl2.c

+ 19
- 0
.gitignore View File

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


+ 94
- 0
Makefile View File

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

+ 11
- 0
TODO View File

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

+ 4
- 0
config/cli.cfg View File

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

+ 5
- 0
config/fx9860g.cfg View File

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

+ 5
- 0
config/fxcg50.cfg View File

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

+ 6
- 0
config/sdl2.cfg View File

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

+ 99
- 0
configure View File

@@ -0,0 +1,99 @@
#! /usr/bin/bash

usage() {
echo "usage: $0 --platform=<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=<toolchain>"
echo " Target triplet; default is 'sh3eb-elf' for fx9860g and 'sh4eb-elf'"
echo " for fxcg50"
echo " --prefix=<path>"
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'."

+ 153
- 0
include/TeX/TeX.h View File

@@ -0,0 +1,153 @@
//---
// TeX: Natural rendering for mathematical formulae
//---

#ifndef TEX_H
#define TEX_H

#include <stddef.h>

//---
// 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 <TeX/structure.h> 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 */

+ 23
- 0
include/TeX/defs.h View File

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

+ 44
- 0
include/TeX/parser.h View File

@@ -0,0 +1,44 @@
//---
// parser: interface to the TeX parser
//---

#ifndef TEX_PARSER
#define TEX_PARSER

#include <TeX/TeX.h>
#include <TeX/structure.h>

/* 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 */

+ 23
- 0
include/TeX/render.h View File

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

+ 153
- 0
include/TeX/structure.h View File

@@ -0,0 +1,153 @@
//---
// structure: Recursive node/flow data structure
//---

#ifndef TEX_STRUCTURE
#define TEX_STRUCTURE

#include <TeX/defs.h>
#include <stddef.h>
#include <stdint.h>

/* 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 */

+ 147
- 0
src/TeX.c View File

@@ -0,0 +1,147 @@
#include <TeX/TeX.h>
#include <TeX/defs.h>
#include <TeX/structure.h>
#include <TeX/parser.h>
#include <TeX/render.h>
#include <stdlib.h>
#include <string.h>

//---
// 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);
}

+ 589
- 0
src/TeX.y View File

@@ -0,0 +1,589 @@
/* TODO Notes for the calculator version.
Don't use GLR (= non-deterministic forks)
Provide <alloca.h>, <malloc.h>, <stddef.h>, <stdlib.h>
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 <TeX/TeX.h>
#include <TeX/parser.h>
#include <TeX/structure.h>

#include <stdlib.h>
#include <string.h>

/* 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 <const char *> TEXT
%token <const char *> COMMAND
%token <const char *> COMMAND_ABS
%left '^' '_'

%type <struct TeX_Flow *> flow
%type <struct TeX_Node *> node
%type <struct TeX_Node *> 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 <stdio.h>

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 */

+ 255
- 0
src/classes.c View File

@@ -0,0 +1,255 @@
//---
// classes: Node classes used for mathematical notations
//---

#include <TeX/TeX.h>
#include <TeX/structure.h>
#include <TeX/render.h>
#include <TeX/defs.h>
#include <string.h>


/* 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;
}

+ 314
- 0
src/flow.c View File

@@ -0,0 +1,314 @@
#include <TeX/TeX.h>
#include <TeX/structure.h>
#include <TeX/render.h>
#include <string.h>

//---
// 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);
}
}

+ 112
- 0
src/platform/cli.c View File

@@ -0,0 +1,112 @@
//---
// CLI interface: for parsing and computation tests on computer
//---

#ifdef TEX_PLATFORM_CLI

#include <TeX/TeX.h>
#include <TeX/structure.h>
#include <TeX/parser.h>

#include <stdio.h>
#include <string.h>

//---
// 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 */

+ 143
- 0
src/platform/sdl2.c View File

@@ -0,0 +1,143 @@
//---
// SDL interface: for rendering tests on computer
//---

#ifdef TEX_PLATFORM_SDL

#include <SDL2/SDL.h>
#include <stdlib.h>
#include <TeX/TeX.h>
#include <TeX/parser.h>

/* 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);