catex 0.0.1+4

  • Readme
  • Changelog
  • Example
  • Installing
  • new81

CaTeX logo #

CaTeX on Pub CaTeX Flutter web demo

CaTeX is a Flutter package that outputs TeX equations (like LaTeX, KaTeX, MathJax, etc.) inline using a widget and Flutter only - no plugins, no web views.

About CaTeX #

Being Dart only, CaTeX renders TeX faster than any other Flutter plugin and is way more flexible. You can view a demo running on the web.

CaTeX is an open source project with the aim of providing a way to render TeX fast in Flutter. This was needed for the simpleclub app, hence, the association. It is also maintained by simpleclub (see the LICENSE file), initially created individually by creativecreatorormaybenot.

Note #

CaTeX is pre-v0.1 release and lacks support for some major TeX functionality. You can help and contribute; see the contributing guide.

To benefit from the library right now, we fall back to an alternative when a specific formula is not supported. CaTeX automatically throws appropriate exceptions for handling this.

Usage #

In order to manually input TeX equations to CaTeX you should use raw Dart strings (r'<input>' or '''<input>'''):

Widget build(BuildContext context) => CaTeX(r'\epsilon = \frac 2 {3 + 2}');

If you fetch the strings from a database, you can just pass them to CaTeX:

CaTeX(equation);

Unsupported input #

If your input is currently not yet supported by CaTeX, you will see proper exceptions thrown for this. See the "Exception handling" section for more on this.

Please create a GitHub issue when you encounter such a case and it is either a bug or a missing feature. You can find issue templates when creating a new issue.

Controlling the font size #

You will notice that the CaTeX widget does not have any parameters for styling (for the sake of simplicity). Instead, it uses inherited widgets to grab necessary properties.

The base font size of the rendered output is controlled via Flutter's DefaultTextStyle widget, i.e. the TextStyle.fontSize property.

Widget build(BuildContext context) {
  return DefaultTextStyle.merge(
    style: TextStyle(
      fontSize: 42,
    ),
    child: CaTeX(r'\CaTeX'),
  );
}

Implementation #

The implementation is mainly based on The TeXbook by Donald E. Knuth, TeX by Topic by Victor Eijkhout, and the KaTeX JavaScript project.

Remember that CaTeX uses Flutter only! Consequently, all parsing and rendering is done in Dart.

The package basically uses two trees from input to displaying all nodes:

  1. Parsing tree, which consists of nodes storing only information about separation, i.e. the current mode (math or text) and the input for a given node.
    Each node in this tree will have the necessary information to create a render object from itself for the rendering tree.
  2. Rendering tree, which makes use of Flutter's render objects and takes care of sizing and rendering (using a canvas).

Fonts & licenses #

The included fonts are taken from the katex-fonts repository and licensed under the SIL Open Font License.
Additionally, some code, e.g. what is used for translating symbols is from KaTeX.
You can find the license for the main KaTeX repo here.

Exception handling #

There are three types of exceptions that are commonly thrown by CaTeX:

  1. ParsingException, which will be thrown if CaTeX does not understand your input, i.e. even before it tries to display your input. This will commonly occur when something about your input is wrong, e.g. the wrong number of arguments for a function.
    This type of exception will be shown on the screen by the default CaTeX widget (like a normal Flutter error).
  2. ConfigurationException, which will be thrown when CaTeX fails to display your input, i.e. when something about your input is not quite right (from CaTeX's point of view). Commonly, this means that something is not configured right, e.g. the name of a symbol.
    This type of exception will also be shown on the screen by the default CaTeX widget (like a normal Flutter error).
  3. RenderingException, which will only be thrown when the constraints given to CaTeX to render your input are not sufficient for your input.
    This type of exception is not shown on screen. Instead, CaTeX will only clip the output. However, you can still see the exception reported by Flutter during performLayout() in the logs.

If you want to customize the look of how ParsingExceptions and ConfigurationExceptions are displayed, consider overriding ErrorWidget.builder. If you want to see CaTeX errors in release mode, you will have to override the builder and ensure that any CaTeXException is handled differently than normal. An example override might look like this:

void main() {
  ErrorWidget.builder = (FlutterErrorDetails details) {
    final exception = details.exception;
    if (exception is CaTeXException) {
      return ErrorWidget(exception);
    }

    var message = '';
    // The assert ensures that any exceptions that are not CaTeX exceptions
    // are not shown in release and profile mode. This ensures that no
    // stack traces or other sensitive information (information that the user
    // is in no way interested in) is shown on screen.
    assert(() {
      message = '${details.exception}\n'
          'See also: https://flutter.dev/docs/testing/errors';
      return true;
    }());

    return ErrorWidget.withDetails(
      message: message,
      error: exception is FlutterError ? exception : null,
    );
  };
  runApp(const App());
}

See the example app for more examples.

Expanding supported functionality #

This package is far from complete in terms of supporting all TeX functionality.
If you want support for more functions, symbols, macros, etc., we appreciate your contribution!
Please refer to the contributing guide to understand how you can easily add functions and more.

To aid our own prioritization for this project, we conducted some research examining how often particular TeX commands appear in some of simpleclub's content. This should give a rough feeling for what is most commonly used. A CSV file with the list of used functions and the frequency of appearance is maintained in the repo.

Mission #

There are already a couple of TeX Flutter plugins out there. So why create another?

Most of those libraries use already existing implementations in other languages, mostly using web views and JavaScript.
This is a valid approach; it is straightforward to implement. You only have to write a wrapper.

However, we believe that utilizing web views is an overhead that makes the applications less portable and performant.
At simpleclub, we recently decided to move from a Flutter-native hybrid to going all-in with Flutter. Now, we need a well-written Flutter TeX library that works on every platform and can display lots of formulas. So we decided to start this open source effort of bringing Dart-native TeX support to Flutter.

A custom TeX parser could potentially also allow us to provide accessibility and screen reader support out of the box.
Here we could build on the work of T.V. Raman, who developed Audio System For Technical Readings (AsTeR) for his PhD.

As this involves a lot of work, we would be happy to work together with the open source for bringing this vision to life together - so everyone can benefit from a pure-Flutter TeX library.

See our contributing guide for more information.

0.0.1+4 #

  • Adjusted alignment of sub and sup elements if both are used at the same time.
  • Added \notin, \neq, \nsubset.

0.0.1+3 #

  • Added \hat implementation that works for small letters.
  • Added \mathbb.

0.0.1+2 #

  • Fixed \frac line positioning.

0.0.1+1 #

  • Expanded color support to include HTML color keywords and hexadecimal strings.
  • Improved documentation.

0.0.1 #

  • Initial release of the CaTeX package.

0.0.0 #

  • Placeholder version.

example/lib/main.dart

import 'package:catex/catex.dart';
import 'package:flutter/material.dart';

/// Example equations to test and showcase the renderer and parser.
List<String> get equations => [
      r'\text{Hello, World!}',
      r'\mu =: \sqrt{x}',
      r'\eta = 7^\frac{4}{2}',
      r'\epsilon = \frac 2 {3 + 2}',
      r'x_{initial} = \frac {20x} {\frac{15}{3}}',
      // ignore: no_adjacent_strings_in_list
      r'\colorbox{red}{bunt} \boxed{ '
          r'\rm{\sf{\bf{'
          r'\textcolor{red} s \textcolor{pink}  i \textcolor{purple}m '
          r'\textcolor{blue}p \textcolor{cyan}  l \textcolor{teal}  e} '
          r'\textcolor{lime}c \textcolor{yellow}l \textcolor{amber} u '
          r'\textcolor{orange} b}}}',
      r'\TeX',
      r'\LaTeX',
      r'\KaTeX',
      r'\CaTeX',
      'x_i=a^n',
      r'\hat{y} = H y',
      r'12^{\frac{\frac{2}{7}}{1}}',
      r'\varepsilon = \frac{\frac{2}{1}}{3}',
      r'\alpha\beta\gamma\delta',
      // ignore: no_adjacent_strings_in_list
      r'\colorbox{black}{\textcolor{white} {black} } \colorbox{white} '
          r'{\textcolor{black} {white} }',
      r'\alpha\ \beta\ \ \gamma\ \ \ \delta',
      r'\epsilon = \frac{2}{3 + 2}',
      r'\tt {type} \textcolor{teal}{\rm{\tt {writer} }}',
      'l = a * t * e * x',
      r'\rm\tt{sp   a c  i n\ \bf\it g}',
      r'5 = 1 \cdot 5',
      '{2 + 3}+{3             +4    }=12',
      r'\backslash \leftarrow \uparrow \rightarrow  \$',
      r'42\uparrow 99\Uparrow\ \  19\downarrow 1\Downarrow',
      '5x =      25',
      r'10\cdot10 = 100',
      'a := 96',
    ];

void main() {
  ErrorWidget.builder = (FlutterErrorDetails details) {
    final exception = details.exception;
    if (exception is CaTeXException) {
      return ErrorWidget(exception);
    }

    var message = '';
    // The assert ensures that any exceptions that are not CaTeX exceptions
    // are not shown in release and profile mode. This ensures that no
    // stack traces or other sensitive information (information that the user
    // is in no way interested in) is shown on screen.
    assert(() {
      message = '${details.exception}\n'
          'See also: https://flutter.dev/docs/testing/errors';
      return true;
    }());

    return ErrorWidget.withDetails(
      message: message,
      error: exception is FlutterError ? exception : null,
    );
  };
  runApp(const App());
}

/// Example app widget that uses [MaterialApp] to display CaTeX output.
class App extends StatelessWidget {
  /// Constructs the example [App].
  const App({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'CaTeX example',
      home: const Home(),
      themeMode: ThemeMode.dark,
      darkTheme: ThemeData.dark(),
    );
  }
}

/// Example home page that includes an infinite list of CaTeX example widgets
/// and a text field to test out CaTeX.
class Home extends StatelessWidget {
  /// Constructs a [Home] widget.
  const Home({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Tap to toggle equations'),
      ),
      body: ListView.builder(
        padding: const EdgeInsets.only(bottom: 16),
        itemBuilder: (context, index) {
          if (index == 0) {
            return const _Highlighted(
              child: _TextFieldEquation(),
            );
          }

          return _Highlighted(
            child: ToggleEquation(
              equations[(index - 1) % equations.length],
            ),
          );
        },
      ),
    );
  }
}

class _TextFieldEquation extends StatefulWidget {
  const _TextFieldEquation({Key key}) : super(key: key);

  @override
  State createState() => _TextFieldEquationState();
}

class _TextFieldEquationState extends State<_TextFieldEquation> {
  TextEditingController _controller;

  bool _input;

  @override
  void initState() {
    super.initState();

    _controller = TextEditingController();
    _input = true;
  }

  @override
  Widget build(BuildContext context) {
    if (_input) {
      return TextField(
        controller: _controller,
        autocorrect: false,
        enableSuggestions: false,
        onSubmitted: (_) {
          setState(() {
            _input = false;
          });
        },
      );
    }

    return InkWell(
      onTap: () {
        setState(() {
          _input = true;
        });
      },
      child: CaTeX(_controller.text),
    );
  }
}

class ToggleEquation extends StatefulWidget {
  const ToggleEquation(this.equation, {Key key}) : super(key: key);

  final String equation;

  @override
  State createState() => _ToggleEquationState();
}

class _ToggleEquationState extends State<ToggleEquation> {
  bool _showSource;

  @override
  void initState() {
    super.initState();

    _showSource = false;
  }

  Widget buildEquation(BuildContext context) {
    if (_showSource) {
      return Text(
        widget.equation,
        // ignore: deprecated_member_use
        style: Theme.of(context).textTheme.subhead,
        textAlign: TextAlign.center,
      );
    }

    return CaTeX(widget.equation);
  }

  void _toggle() {
    setState(() {
      _showSource = !_showSource;
    });
  }

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: _toggle,
      child: buildEquation(context),
    );
  }
}

class _Highlighted extends StatelessWidget {
  const _Highlighted({Key key, this.child}) : super(key: key);

  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8),
      child: Card(
        color: Colors.grey[800],
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: DefaultTextStyle.merge(
            style: const TextStyle(
              fontSize: 24,
            ),
            child: Center(
              child: child,
            ),
          ),
        ),
      ),
    );
  }
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  catex: ^0.0.1+4

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:catex/catex.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
66
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
90
Overall:
Weighted score of the above. [more]
81
Learn more about scoring.

We analyzed this package on Jul 14, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.8.4
  • pana: 0.13.15
  • Flutter: 1.17.5

Analysis suggestions

Package not compatible with SDK dart

Because:

  • catex that is a package requiring null.

Maintenance suggestions

Package is pre-v0.1 release. (-10 points)

While nothing is inherently wrong with versions of 0.0.*, it might mean that the author is still experimenting with the general direction of the API.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.8.4 <3.0.0
flutter 0.0.0
meta ^1.1.8 1.1.8 1.2.2
Transitive dependencies
collection 1.14.12 1.14.13
sky_engine 0.0.99
typed_data 1.1.6 1.2.0
vector_math 2.0.8 2.1.0-nullsafety
Dev dependencies
flutter_test
lint ^1.2.0