flutter_tex_js 3.0.0 copy "flutter_tex_js: ^3.0.0" to clipboard
flutter_tex_js: ^3.0.0 copied to clipboard

A lightweight TeX plugin that uses KaTeX (JavaScript) in a native web view to render to PNG

example/lib/main.dart

import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:flutter_tex_js/flutter_tex_js.dart';
import 'package:flutter_tex_js_example/comparison/comparison.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.light(),
      darkTheme: ThemeData.dark(),
      home: Scaffold(
        appBar: AppBar(title: const Text('Flutter TeX JS Example')),
        body: const _EditableExample(),
        floatingActionButton: const _LaunchComparisonButton(),
      ),
    );
  }
}

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

  @override
  _EditableExampleState createState() => _EditableExampleState();
}

class _EditableExampleState extends State<_EditableExample> {
  late TextEditingController _textEditingController;
  late bool _displayMode;
  late double _fontSize;
  late Alignment _alignment;

  @override
  void initState() {
    super.initState();
    _textEditingController = TextEditingController(
      text: r'a=\pm\sqrt{b^2+c^2} \int_\infty^\beta d\gamma',
    );
    _displayMode = true;
    _alignment = Alignment.center;
  }

  @override
  void didChangeDependencies() {
    _fontSize = DefaultTextStyle.of(context).style.fontSize!;
    super.didChangeDependencies();
  }

  @override
  void dispose() {
    _textEditingController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Padding(
        padding: const EdgeInsets.all(8),
        child: ListView(
          children: [
            TextField(controller: _textEditingController),
            const SizedBox(height: 8),
            _DisplayModeListTile(
              value: _displayMode,
              onChanged: (value) => setState(() => _displayMode = value),
            ),
            _FontSizeListTile(
              value: _fontSize,
              onChanged: (value) => setState(() => _fontSize = value),
            ),
            _AlignmentListTile(
              onChanged: (value) => setState(() => _alignment = value),
            ),
            const SizedBox(height: 8),
            ValueListenableBuilder<TextEditingValue>(
              valueListenable: _textEditingController,
              builder: (context, value, child) {
                return AnimatedAlign(
                  duration: const Duration(seconds: 1),
                  alignment: _alignment,
                  child: ColoredBox(
                    color: Colors.amber,
                    child: TexImage(
                      value.text,
                      displayMode: _displayMode,
                      fontSize: _fontSize,
                    ),
                  ),
                );
              },
            ),
            const SizedBox(height: 24),
            const Center(child: Text('Horizontal scroll')),
            const SizedBox(height: 8),
            ValueListenableBuilder<TextEditingValue>(
              valueListenable: _textEditingController,
              builder: (context, value, child) => SingleChildScrollView(
                scrollDirection: Axis.horizontal,
                child: TexImage(
                  [value.text, value.text, value.text].join(' '),
                  displayMode: _displayMode,
                  fontSize: _fontSize,
                ),
              ),
            ),
            const Center(
              child: Text(
                'fin',
                style: TextStyle(fontStyle: FontStyle.italic),
              ),
            ),
            const SizedBox(height: 24),
            const Center(child: Text('Long text')),
            const SizedBox(height: 8),
            const _LongTextExample(),
            const SizedBox(height: 24),
            const Center(child: Text('Environments')),
            const SizedBox(height: 8),
            const _EnvironmentsExample(),
          ],
        ),
      ),
    );
  }
}

class _LaunchComparisonButton extends StatelessWidget {
  const _LaunchComparisonButton({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      child: const Icon(Icons.all_inclusive),
      onPressed: () => ComparisonPage.pushRoute(context),
    );
  }
}

class _DisplayModeListTile extends StatelessWidget {
  const _DisplayModeListTile(
      {required this.value, required this.onChanged, Key? key})
      : super(key: key);

  final bool value;
  final ValueChanged<bool> onChanged;

  @override
  Widget build(BuildContext context) {
    return CheckboxListTile(
      value: value,
      onChanged: (value) => onChanged(value!),
      title: const Text('Display mode'),
    );
  }
}

class _FontSizeListTile extends StatelessWidget {
  const _FontSizeListTile({
    required this.value,
    required this.onChanged,
    Key? key,
  }) : super(key: key);

  final double value;
  final ValueChanged<double> onChanged;

  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: const Text('Font size'),
      trailing: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Text('$value px',
              style: const TextStyle(
                  fontFeatures: [FontFeature.tabularFigures()])),
          IconButton(
            icon: const Icon(Icons.remove),
            onPressed: () => onChanged(value - 1),
          ),
          IconButton(
            icon: const Icon(Icons.add),
            onPressed: () => onChanged(value + 1),
          ),
        ],
      ),
    );
  }
}

class _AlignmentListTile extends StatelessWidget {
  const _AlignmentListTile({required this.onChanged, Key? key})
      : super(key: key);

  final ValueChanged<Alignment> onChanged;

  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: const Text('Animate'),
      trailing: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          IconButton(
            icon: const Icon(Icons.arrow_left),
            onPressed: () => onChanged(Alignment.centerLeft),
          ),
          IconButton(
            icon: const Icon(Icons.adjust),
            onPressed: () => onChanged(Alignment.center),
          ),
          IconButton(
            icon: const Icon(Icons.arrow_right),
            onPressed: () => onChanged(Alignment.centerRight),
          )
        ],
      ),
    );
  }
}

// Text from https://en.wikipedia.org/wiki/Electric_field#Electric_potential
class _LongTextExample extends StatelessWidget {
  const _LongTextExample({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) => const Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text.rich(
            TextSpan(
              text:
                  "If a system is static, such that magnetic fields are not time-varying, then by Faraday's law, the electric field is curl-free. In this case, one can define an electric potential, that is, a function ",
              children: [
                WidgetSpan(child: TexImage(r'\Phi', displayMode: false)),
                TextSpan(text: ' such that '),
                WidgetSpan(
                  child: TexImage(
                    r'\mathbf{E} = -\nabla \Phi',
                    displayMode: false,
                  ),
                ),
                TextSpan(
                  text:
                      '. This is analogous to the gravitational potential. The difference between the electric potential at two points in space is called the potential difference (or voltage) between the two points.\n',
                ),
              ],
            ),
          ),
          Text.rich(
            TextSpan(
              text:
                  'In general, however, the electric field cannot be described independently of the magnetic field. Given the magnetic vector potential, ',
              children: [
                TextSpan(
                  text: 'A',
                  style: TextStyle(fontWeight: FontWeight.bold),
                ),
                TextSpan(text: ', defined so that '),
                WidgetSpan(
                  child: TexImage(
                    r'\mathbf{B} = \nabla \times \mathbf{A}',
                    displayMode: false,
                  ),
                ),
                TextSpan(
                  text: ', one can still define an electric potential ',
                ),
                WidgetSpan(child: TexImage(r'\Phi', displayMode: false)),
                TextSpan(text: ' such that:')
              ],
            ),
          ),
          SizedBox(height: 8),
          TexImage(
            r'\mathbf{E} = - \nabla \Phi - \frac { \partial \mathbf{A} } { \partial t }',
          ),
          SizedBox(height: 8),
          Text.rich(
            TextSpan(
              text: 'Where ',
              children: [
                WidgetSpan(child: TexImage(r'\Phi', displayMode: false)),
                TextSpan(
                  text: ' is the gradient of the electric potential and ',
                ),
                WidgetSpan(
                  child: TexImage(
                    r'\frac { \partial \mathbf{A} } { \partial t }',
                    displayMode: false,
                  ),
                ),
                TextSpan(
                  text:
                      ' is the partial derivative of A with respect to time.\n',
                ),
              ],
            ),
          ),
          Text(
            "Faraday's law of induction can be recovered by taking the curl of that equation",
          ),
          SizedBox(height: 8),
          TexImage(
            r'\nabla \times \mathbf{E} = -\frac{\partial (\nabla \times \mathbf{A})} {\partial t}= -\frac{\partial \mathbf{B}} {\partial t}',
          ),
          SizedBox(height: 8),
          Text.rich(
            TextSpan(
              text: 'which justifies, a posteriori, the previous form for ',
              children: [
                TextSpan(
                  text: 'E',
                  style: TextStyle(fontWeight: FontWeight.bold),
                ),
                TextSpan(text: '.'),
              ],
            ),
          ),
        ],
      );
}

class _EnvironmentsExample extends StatelessWidget {
  const _EnvironmentsExample({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return const Column(
      children: [
        TexImage(r'''\begin{equation}
a = b + c
\end{equation}'''),
        SizedBox(height: 8),
        TexImage(r'''\begin{Bmatrix}
   a & b \\
   c & d
   \end{Bmatrix}'''),
        SizedBox(height: 8),
        TexImage(r'''\begin{cases}
   a &\text{if } b \\
   c &\text{if } d
   \end{cases}'''),
        SizedBox(height: 8),
        TexImage(r'''\begin{CD}
   A @>a>> B \\
@VbVV @AAcA \\
   C @= D
   \end{CD}'''),
        SizedBox(height: 8),
        TexImage(r'''\begin{equation}
\begin{split}
   a &=b+c\\
      &=e+f
\end{split}
\end{equation}'''),
        SizedBox(height: 8),
        TexImage(r'''\begin{Vmatrix}
   a & b \\
   c & d
\end{Vmatrix}''')
      ],
    );
  }
}
10
likes
140
pub points
77%
popularity

Publisher

verified publishermadlon-kay.com

A lightweight TeX plugin that uses KaTeX (JavaScript) in a native web view to render to PNG

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (LICENSE)

Dependencies

flutter, flutter_tex_js_android, flutter_tex_js_ios, flutter_tex_js_platform_interface

More

Packages that depend on flutter_tex_js