libtexce, LaTeX Rendering on the TI-84 Plus CE




GitHub Repository


libtexce renders real LaTeX math directly on your TI-84 Plus CE. Fractions, integrals, summations, matrices, Greek letters, square roots, auto-scaling delimiters, all drawn natively on the calculator.

149 LaTeX commands are supported, across 6 matrix environments, with full subscript/superscript nesting, auto-sizing brackets, and inline or display math. Documents can be arbitrarily long with smooth scrolling and no proportional memory cost. The entire engine is written in C and fits comfortably on the ez80.

Quick Start

The full rendering pipeline is five steps: load fonts, configure, format, create a renderer, draw.


Code:
#include <fontlibc.h>
#include <graphx.h>
#include <keypadc.h>
#include <tice.h>

#include "tex/tex.h"

int main(void)
{
    gfx_Begin();
    gfx_SetDrawBuffer();
    gfx_SetTransparentColor(255);

    fontlib_font_t* font_main   = fontlib_GetFontByIndex("TeXFonts", 0);
    fontlib_font_t* font_script = fontlib_GetFontByIndex("TeXScrpt", 0);
    if (!font_main || !font_script) {
        gfx_PrintStringXY("Missing font packs!", 10, 10);
        gfx_SwapDraw();
        while (!os_GetCSC());
        gfx_End();
        return 1;
    }

    // set fonts (global state, call once)
    tex_draw_set_fonts(font_main, font_script);
    fontlib_SetTransparency(true);
    fontlib_SetForegroundColor(0);
    fontlib_SetBackgroundColor(255);

    // prepare a mutable input buffer.
    // tex_format() tokenizes in place, the buffer must be writable
    // and must remain allocated as long as the layout exists
    const char* source =
        "Quadratic Formula\n"
        "$$x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}$$\n"
        "\n"
        "Taylor Series\n"
        "$$f(x) \\approx f(a) + f'(a)(x-a) + \\frac{f''(a)}{2}(x-a)^2$$";

    size_t len = strlen(source);
    char* buf = malloc(len + 1);
    memcpy(buf, source, len + 1);

    // format parses the document and computes layout metrics
    TeX_Config cfg = {
        .color_fg  = 0,       // black
        .color_bg  = 255,     // white
        .font_pack = "TeXFonts",
    };
    int margin = 10;
    TeX_Layout* layout = tex_format(buf, GFX_LCD_WIDTH - margin * 2, &cfg);

    // create a renderer (manages a transient memory pool for drawing)
    TeX_Renderer* renderer = tex_renderer_create();

    int scroll_y = 0;
    int total_h  = tex_get_total_height(layout);
    int max_scroll = total_h > GFX_LCD_HEIGHT ? total_h - GFX_LCD_HEIGHT : 0;

    // draw loop
    while (true) {
        kb_Scan();
        if (kb_Data[6] & kb_Clear) break;
        if (kb_Data[7] & kb_Up)   scroll_y -= 10;
        if (kb_Data[7] & kb_Down) scroll_y += 10;
        if (scroll_y < 0) scroll_y = 0;
        if (scroll_y > max_scroll) scroll_y = max_scroll;

        gfx_FillScreen(255);
        tex_draw(renderer, layout, margin, 0, scroll_y);
        gfx_SwapDraw();
    }

    // cleanup order matters: renderer, layout, then buffer
    tex_renderer_destroy(renderer);
    tex_free(layout);
    free(buf);
    gfx_End();
    return 0;
}


Real World Usage

If you are building a notes app (the most common use case), start with libtexce_notes_template. It gives you a ready to use TI-84 Plus CE notes workflow with formatting and CI-built transfer artifacts.

For a larger production example, see matrix, a full linear algebra app that uses libtexce to render step by step formatted math on device.

Configuration


Code:
typedef struct {
    uint8_t      color_fg;        // Foreground color (palette index, 0-255)
    uint8_t      color_bg;        // Background color (palette index, 0-255)
    const char*  font_pack;       // Font pack name (default: "TeXFonts")
    TeX_ErrorLogFn error_callback; // Optional error/warning callback
    void*        error_userdata;   // Passed to callback
} TeX_Config;


Colors are 8 bit palette indices matching the graphx palette. The error callback receives a severity level (0 = info, 1 = warning, 2 = error), a message string, and in debug builds, the source file and line number where the error occurred.

Ownership and Lifetime Rules

Understanding buffer ownership is important for correct usage.

The Input Buffer

tex_format() tokenizes the input buffer in place. After the call, the buffer's contents are modified (null terminators are inserted between tokens). You should consider the buffer opaque after passing it to tex_format(). Do NOT attempt to read, modify, or reason about its contents.

The buffer must remain allocated and at the same address for the entire lifetime of the TeX_Layout. This is because tex_draw() reparses the source text from the buffer on every frame. The layout stores a pointer to the buffer, not a copy.

Cleanup order matters:

Code:
// correct: free in reverse order of creation
tex_renderer_destroy(renderer);
tex_free(layout);
free(buf);

// wrong: freeing buffer while layout still exists
free(buf);           // dangling pointer in layout->source
tex_free(layout);    // undefined behavior


The Renderer

A TeX_Renderer owns a slab of memory used as a transient pool. Each call to tex_draw() may reset and reuse this pool. A single renderer can be shared across multiple layouts. It has no permanent binding to any particular layout.

How Rendering Works

libtexce uses a streaming two pass architecture designed for the calculator's constrained memory.

Pass 1: tex_format() (Dry Run Layout)

When you call tex_format(), the engine tokenizes and parses the entire document, measuring each line's height and accumulating the total document height. No nodes or render trees are retained, only the total height and a sparse checkpoint index are stored in the TeX_Layout.

Checkpoints record (y_position, source_pointer) pairs at regular pixel intervals (~200px). These allow tex_draw() to jump into the middle of a long document without reparsing from the beginning.

Pass 2: tex_draw() (Windowed Reparse)

Each time tex_draw() is called, the renderer:


  • Checks its cache. If the scroll position falls within the previously hydrated window, the existing render tree is reused without reparsing.
  • Otherwise, rehydrates. The renderer finds the nearest checkpoint before scroll_y - padding, reparses from that point forward, and builds a render tree covering scroll_y ± 240px (one screen of padding in each direction).
  • Draws the visible lines from the render tree to the current graphx draw buffer.


This means the renderer only ever holds nodes for ~3 screens of content, regardless of total document length. The tradeoff is that scrolling to a completely new region triggers a reparse, but checkpoint indexing keeps this fast.

Why This Matters to You


  • Documents can be arbitrarily long without proportional memory cost.
  • The input buffer must stay alive because tex_draw() reads from it on every cache miss.
  • Renderer slab sizing (tex_renderer_create_sized()) controls the upper bound on how much visible content can be rendered. The default 40 KB is generous (and more often than not too much) for most documents. Use tex_renderer_get_stats() to measure actual usage.


Font System and Assets

libtexce uses two custom font packs stored as appvars:


  • TeXFonts.8xv, main text and math font (16 px)
  • TeXScrpt.8xv, script/subscript/superscript font (12 px)


Both must be transferred to the calculator before running any program that uses libtexce. Prebuilt copies are in the assets/ directory. These do not look great. Contributions are welcome.

Each font pack provides the full ASCII range (0x20 to 0x7F) plus custom math symbols: Greek letters (α through Ω), calculus symbols (∂, ∞, ∇), big operators (∫, Σ, Π), operators, arrows, logic symbols, and structural glyphs. The LaTeX parser maps \alpha, \int, etc. automatically.

Supported LaTeX

libtexce supports a substantial subset of LaTeX math mode. The full list is maintained on GitHub.

Highlights:


  • Fractions: \frac{a}{b}, \tfrac, \binom{n}{k}
  • Roots: \sqrt{x}, \sqrt[3]{x}
  • Scripts: x^2, x_n, x_i^2
  • Greek: \alpha through \omega, \Gamma through \Omega
  • Big operators: \int, \sum, \prod with limits, plus \iint, \iiint, \oint variants
  • Functions: \sin, \cos, \lim, \log, \exp, \det, \gcd, and many more
  • Accents: \hat, \bar, \vec, \dot, \tilde, \overline, \underline
  • Decorations: \overbrace{...}^{label}, \underbrace{...}_{label}
  • Delimiters: \left( ... \right) with auto-sizing for (), [], {}, ||, ⟨⟩, floor, ceil
  • Matrices: pmatrix, bmatrix, Bmatrix, vmatrix, matrix, array (with column separators)
  • Spacing: \, \: \; \! \quad \qquad
  • Text mode: \text{...} for roman text within math


Input uses standard LaTeX delimiters: $...$ for inline math, $$...$$ for display math (centered). Everything outside $ delimiters is rendered as plain text with automatic word wrapping.

Common Patterns

Multiple Layouts with a Shared Renderer

A single TeX_Renderer can draw different layouts on different frames. This is useful for chat style UIs:


Code:
TeX_Renderer* renderer = tex_renderer_create();

// each message gets its own layout and buffer
char* buf1 = strdup("What is $E = mc^2$?");
TeX_Layout* msg1 = tex_format(buf1, width, &cfg);

char* buf2 = strdup("Einstein's mass-energy equivalence:\n$$E = mc^2$$");
TeX_Layout* msg2 = tex_format(buf2, width, &cfg);

// draw them at different positions using the same renderer
tex_draw(renderer, msg1, x, y1, 0);
tex_draw(renderer, msg2, x, y2, 0);

// cleanup
tex_renderer_destroy(renderer);
tex_free(msg1); tex_free(msg2);
free(buf1); free(buf2);


When switching between layouts, the renderer invalidates its cache and reparses. For a scrolling view of a single layout, the cache avoids redundant work.

Error Handling


Code:
TeX_Layout* layout = tex_format(buf, width, &cfg);

if (!layout) {
    // catastrophic failure (OOM during initialization)
}

if (tex_get_last_error(layout) != TEX_OK) {
    // parse error, font error, etc.
    // the layout may still be partially renderable
    dbg_printf("TeX error: %s (code %d, detail %d)\n",
        tex_get_error_message(layout),
        tex_get_last_error(layout),
        tex_get_error_value(layout));
}


Building

libtexce uses CMake with presets. There are two independent build systems: the native/WASM host build (for development and testing), and the CE build (for the actual calculator). For full build instructions, see the GitHub README.

CE Build (Calculator)

The CE build uses the CE C/C++ Toolchain:


Code:
cd demo/ce
cmake --preset ce
cmake --build ../../build/ce


This produces .8xp files. Transfer the .8xp program along with TeXFonts.8xv, TeXScrpt.8xv, and clibs.8xg to the calculator.

Native Build (Development + Tests)

Requires Clang, CMake ≥ 3.20, Ninja, SDL2, and SDL2_mixer:


Code:
cmake --preset native
cmake --build build/native
./build/native/bin/demo_text_portce


Integrating into Your CE Project

  • Copy (or git submodule) the src/tex/ directory and include/texfont.h into your project.
  • Add include paths for src/, src/tex/, and include/.
  • Transfer TeXFonts.8xv, TeXScrpt.8xv, and clibs.8xg alongside your .8xp.
  • If using CMake, see demo/ce/CMakeLists.txt for a complete working example.

License

libtexce is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0-only).

See the LICENSE for the full text.
Wow, this is super impressive! Rendering looks fast and everything is very clean.

What do you have planned next? Like you said, an obvious next step would be to use this for something like a notes program, but maybe something else, like a custom calculator program, could be neat too.

EDIT: Hadn't read this thread quite yet, looks like you're one step ahead of me. Really excellent job.
Wow, this was very unexpected! Maybe I'll need to try remaking Csym to use this, it seems much better for that 🙂
  
Register to Join the Conversation
Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.

» Go to Registration page
Page 1 of 1
» All times are UTC - 5 Hours
 
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

 

Advertisement