@ -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 | |||
@ -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: |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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'." |
@ -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 */ |
@ -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 */ |
@ -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 */ |
@ -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 */ |
@ -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 */ |
@ -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); | |||
} |
@ -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 */ |
@ -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; | |||
} |
@ -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 |