val_latex_flutter 0.1.2
val_latex_flutter: ^0.1.2 copied to clipboard
Render LaTeX math in Flutter: formula widgets, math inside text and Markdown, line wrapping, selection, screen readers, theming and streaming LLM output.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'demos/accessibility.dart';
import 'demos/background.dart';
import 'demos/basics.dart';
import 'demos/canvas.dart';
import 'demos/chemistry.dart';
import 'demos/compat.dart';
import 'demos/core.dart';
import 'demos/equations.dart';
import 'demos/errors.dart';
import 'demos/interaction.dart';
import 'demos/lists.dart';
import 'demos/macros.dart';
import 'demos/math_span.dart';
import 'demos/overflow.dart';
import 'demos/rtl.dart';
import 'demos/scripts.dart';
import 'demos/streaming.dart';
import 'demos/tables.dart';
import 'demos/text_with_math.dart';
import 'demos/theme.dart';
import 'demos/wrapping.dart';
import 'tour.dart';
void main() => runApp(const ExampleApp());
/// Every feature, in the order shown.
final List<Feature> features = [
Feature(
id: 'basics',
title: 'Formulas',
description:
'Math.tex renders a formula, inline or in display style, at any '
'size and colour.',
file: 'basics.dart',
demo: (_) => const BasicsDemo(),
),
Feature(
id: 'text',
title: 'Text with formulas (Markdown)',
description:
r'MathText renders text with $…$ and $$…$$ formulas in it — what a '
'Markdown or chat renderer needs.',
file: 'text_with_math.dart',
demo: (_) => const TextWithMathDemo(),
),
Feature(
id: 'span',
title: 'Formulas in rich text',
description:
'MathSpan puts a formula inside Text.rich, sitting on the text '
'baseline.',
file: 'math_span.dart',
demo: (_) => const MathSpanDemo(),
),
Feature(
id: 'streaming',
title: 'Streaming text',
description:
'Text that is still arriving renders without flashing raw TeX: '
'unfinished formulas show as far as they go.',
file: 'streaming.dart',
demo: (_) => const StreamingDemo(),
),
Feature(
id: 'wrapping',
title: 'Line wrapping',
description:
'Display formulas break to the width they get, at the places a '
'person would. Drag the width.',
file: 'wrapping.dart',
demo: (_) => const WrappingDemo(),
),
Feature(
id: 'overflow',
title: 'Overflow',
description:
'What a formula that does not wrap does when it is too wide: '
'scroll, shrink or clip.',
file: 'overflow.dart',
demo: (_) => const OverflowDemo(),
),
Feature(
id: 'equations',
title: 'Equations, numbers, matrices',
description:
'align, gather, equation and friends, numbered or tagged; cases, '
'matrices and arrays.',
file: 'equations.dart',
demo: (_) => const EquationsDemo(),
),
Feature(
id: 'errors',
title: 'Errors and unfinished input',
description:
'Bad input never throws: mistakes show in place, or your own '
'fallback; unfinished input renders what exists.',
file: 'errors.dart',
demo: (_) => const ErrorsDemo(),
),
Feature(
id: 'theme',
title: 'Theming',
description:
r'MathTheme sets colours, styles parts marked with \htmlClass, and '
'can draw a layout debug overlay.',
file: 'theme.dart',
demo: (_) => const ThemeDemo(),
),
Feature(
id: 'interaction',
title: 'Taps, highlights, selection',
description:
'Find out which part was tapped, highlight parts of the source, '
'and select and copy formulas as LaTeX.',
file: 'interaction.dart',
demo: (_) => const InteractionDemo(),
),
Feature(
id: 'accessibility',
title: 'Accessibility',
description:
'Screen readers hear the formula spoken (English or Arabic), part '
'by part; keyboard users can move through it.',
file: 'accessibility.dart',
demo: (_) => const AccessibilityDemo(),
),
Feature(
id: 'scripts',
title: 'Text in any script',
description:
r'\text{…} is shaped by Flutter: Arabic joins, Indic conjuncts form, '
'CJK and emoji work.',
file: 'scripts.dart',
demo: (_) => const ScriptsDemo(),
),
Feature(
id: 'rtl',
title: 'Right-to-left math',
description:
'Arabic notation: mirrored layout, Arabic-Indic digits, Arabic '
'operator names.',
file: 'rtl.dart',
demo: (_) => const RtlDemo(),
),
Feature(
id: 'chemistry',
title: 'Chemistry, units, physics',
description:
r'\ce, \SI and physics (\dv, \pdv, \expval…) work out of the box; '
r'the full physics package is one option away.',
file: 'chemistry.dart',
demo: (_) => const ChemistryDemo(),
),
Feature(
id: 'tables',
title: 'Tables',
description:
r'tabular, tabularx and table with \caption; booktabs rules '
r'(\toprule, \midrule, \cmidrule) and \multicolumn.',
file: 'tables.dart',
demo: (_) => const TablesDemo(),
),
Feature(
id: 'lists',
title: 'Lists and package commands',
description:
r'itemize, enumerate, description, \intertext, cases*; and \abs, '
r'\dv, \sfrac, \vv, \celsius… without loading a package.',
file: 'lists.dart',
demo: (_) => const ListsDemo(),
),
Feature(
id: 'macros',
title: 'Shared macros',
description:
r'Define \R or \norm once and use them in every formula that gets '
'the same macro table.',
file: 'macros.dart',
demo: (_) => const MacrosDemo(),
),
Feature(
id: 'canvas',
title: 'Paint on a canvas',
description:
'Lay a formula out once and draw it yourself with MathPainter, in '
'any CustomPainter.',
file: 'canvas.dart',
demo: (_) => const CanvasDemo(),
),
Feature(
id: 'core',
title: 'The pure-Dart core',
description:
'Parse, measure, normalise, speak and find formulas in text without '
'any widget — also on a server or in an isolate.',
file: 'core.dart',
demo: (_) => const CoreDemo(),
),
Feature(
id: 'background',
title: 'Background layout',
description:
'Very large formulas can be laid out in a background isolate, so '
'the UI never stutters.',
file: 'background.dart',
demo: (_) => const BackgroundDemo(),
),
Feature(
id: 'compat',
title: 'Moving from flutter_math_fork',
description:
'Change one import and keep your code: the compat layer has '
"flutter_math_fork's API.",
file: 'compat.dart',
demo: (_) => const CompatDemo(),
),
];
/// The tour of val_latex_flutter's features.
class ExampleApp extends StatefulWidget {
/// Creates the app.
const ExampleApp({super.key});
@override
State<ExampleApp> createState() => _ExampleAppState();
}
class _ExampleAppState extends State<ExampleApp> {
var _dark = false;
@override
Widget build(BuildContext context) => MaterialApp(
title: 'val_latex_flutter',
debugShowCheckedModeBanner: false,
theme: ThemeData(colorSchemeSeed: Colors.indigo),
darkTheme: ThemeData(
colorSchemeSeed: Colors.indigo,
brightness: Brightness.dark,
),
themeMode: _dark ? ThemeMode.dark : ThemeMode.light,
home: TourPage(
dark: _dark,
onToggleDark: () => setState(() => _dark = !_dark),
),
);
}
/// The features, one card each, with a list to jump between them.
class TourPage extends StatefulWidget {
/// Creates the page.
const TourPage({super.key, required this.dark, required this.onToggleDark});
/// Whether the dark theme is on.
final bool dark;
/// Switches between light and dark.
final VoidCallback onToggleDark;
@override
State<TourPage> createState() => _TourPageState();
}
class _TourPageState extends State<TourPage> {
final _keys = {for (final f in features) f.id: GlobalKey()};
void _jump(String id) {
final context = _keys[id]!.currentContext;
if (context != null) {
Scrollable.ensureVisible(
context,
duration: const Duration(milliseconds: 300),
);
}
}
@override
Widget build(BuildContext context) {
final wide = MediaQuery.sizeOf(context).width >= 1100;
final list = ListView(
padding: const EdgeInsets.symmetric(vertical: 8),
children: [
for (final f in features)
ListTile(
dense: true,
title: Text(f.title),
onTap: () {
if (!wide) Navigator.of(context).pop();
_jump(f.id);
},
),
],
);
final content = SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 1200),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const _Intro(),
for (final f in features) ...[
const SizedBox(height: 24),
FeatureCard(f, key: _keys[f.id]),
],
],
),
),
),
);
return Scaffold(
appBar: AppBar(
title: const Text('val_latex_flutter'),
actions: [
IconButton(
tooltip: widget.dark ? 'Light theme' : 'Dark theme',
icon: Icon(widget.dark ? Icons.light_mode : Icons.dark_mode),
onPressed: widget.onToggleDark,
),
],
),
drawer: wide ? null : Drawer(child: SafeArea(child: list)),
body: wide
? Row(
children: [
SizedBox(width: 260, child: list),
const VerticalDivider(width: 1),
Expanded(child: content),
],
)
: content,
);
}
}
class _Intro extends StatelessWidget {
const _Intro();
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'TeX math for Flutter, in pure Dart',
style: theme.textTheme.headlineSmall,
),
const SizedBox(height: 8),
Text(
'Every feature of val_latex_flutter, live, with the code that '
'makes it — copy any snippet with the button in its corner. Add '
'the package and import it:',
style: theme.textTheme.bodyLarge,
),
const SizedBox(height: 8),
const SnippetText(
"import 'package:val_latex_flutter/val_latex_flutter.dart';",
),
],
);
}
}