How to dynamically render TeX equations in input text field in Flutter? - flutter

TL;DR
I need to render LaTeX equations in a Flutter text input field while the user is typing
The Goal
I am currently working on a Flutter project for both web/desktop where I need to be able to render LaTeX/TeX equations in an input field while the user is typing. For example, if the user types “sqrt”, a square root symbol appears where the sqrt was. This will allow for easy math input of complex functions, where the user can type something like:
“int 1 2 sqrt(sin(x/2)^2)”
While typing, this equation would continuously render in the same input box and end up looking like:
Example Equation Render
I plan to allow the user to use a lot of different mathematical symbols/functions.
An extremely similar implementation of what I am trying to achieve is the MathQuill project. However while MathQuill works well with JavaScript and React, I have not found a way to use it in Flutter. If there is a way to port it into Flutter that would work perfectly for my needs.
Requirements and Preferences
Requirements
Render the TeX in the same input box that they are typing in.
Render TeX in real time. The user should be able to see changes in their current text box while typing. A good (but very simple) demo of what I would like can be seen in the math entry boxes on Desmos. I do not want the user to have to press Enter to see their changes, it should happen while typing.
Cannot be click-based equation entry. I want the user to be able to type “sqrt”, “int”, “prod”, “sum”, etc. ... not click on a square root or integral button with some sort of on-screen keyboard. (For example, Flutter’s math_keyboard would not work for my project)
Flutter-based solution.
Preferences
I would prefer to use the KaTeX rendering engine due to it’s substantially increased speed over MathJax. I am still flexible here.
Current Attempts
I have looked through many packages on pub.dev, but have not been able to find a suitable package that suits my use case. I have managed to display TeX on the webpage using the flutter_tex package. For example:
Code:
import 'package:flutter/material.dart';
import 'package:flutter_tex/flutter_tex.dart';
void main() => runApp(TexApp());
class TexApp extends StatelessWidget {
const TexApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Testing flutter_tex")),
body: TeXView(
child: TeXViewColumn(children: [
// This widget displays data perfectly as it should, but it is static and
// so the user cannot dynamically edit the cell.
TeXViewDocument(r"""<p>
$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}$$</p>""",
style: TeXViewStyle.fromCSS(
'padding: 15px; color: black; background: white')),
]))),
);
}
}
Resulting webpage:
Resulting TeX equations in Flutter web application
However, the above implementation does not allow continuous input/dynamic rendering as the user types their equations which I need for my project.
I am new to Flutter and web development as a whole. However, I spent a lot of time learning React and Flutter and I am certain that I want to use Flutter moving forward for a variety of reasons.
Any help here would be extremely appreciated!
UPDATE: As of August 2022 I still have not found a solution to this issue!

Related

Allow accessibility screen reader to read a number string one-by-one as digits using semantics [flutter]

I'm using Semantics to adapt my app for accessibility and want to read a phone number (like: Text("950874123")) one to one instead "million, thousand...". Is there any way to do it?
I see this was asked a while back, but I ran into the same problem. I'm adding my solution here to help out the next guy.
I got the solution with the help of this page:
https://medium.com/theotherdev-s/mastering-flutter-semantics-672440bc8bc8
Basically, my solution is to use the Semantics label (that I can control) and use ExcludeSemantics to exclude the text that I'm reading. In the label, I divide everything up into an array that I convert into a string. This makes the screen reader see everything as individual numbers.
Here's an example
Semantics(
label: numberString.split("").toString(),
child: ExcludeSemantics(
excluding: true,
child: Text(numberString),
)
)
*Edit:
After playing around more with the code, I realized that the Text field has a property called "semanticsLabel" where you can do the same thing. I left the above example since it can be used for other widget types, not just Text()
This is what the code would look like:
Semantics(
child: Text(
viewModel.confirmationId,
semanticsLabel:numberString.split("").toString(),
)
)
(additionally, the parent 'Semantics' widget probably isn't needed. The label will work directly with the Text widget)

Material-UI Sizing Confusion

In the sizing documentation, it says "Easily make an element as wide or as tall (relative to its parent) with the width and height utilities.".
What I realize is that almost all the examples in system section of documentation, examples mostly related to Box component. I tried some of the tricks from there on Button element. And expectedly nothing changed.
Well for those who is new on material (actually UI) this is a bit tricky. first, they use Box because as per box documentation they said box generated using material styled (not the styled-components) "The Box component packages all the style functions that are exposed in #material-ui/system. It's created using the styled() function of #material-ui/core/styles."
So, I went to the github and material core repository to understand how to do that.
export const styleFunction = css(
compose(
borders,
display,
flexbox,
grid,
positions,
palette,
shadows,
sizing,
spacing,
typography,
),
);
css and compose are also exported from #material-ui/system
then at the end of the file;
const Box = styled(BoxRoot, { shouldForwardProp }, { muiName: 'MuiBox' })(styleFunction);
export default Box;
The styled used here is from an experimentalStyled package (an internal package) but that does not matter. Cuz, Material guys exporting a styled function/hook for our use.
import { styled } from "#material-ui/core/styles";
I tried this styled function for Button component.
const CustomButton = styled(Button)(sizing);
adn yes it worked. I am now able to apply magical styling skills defined in the system section of material UI.
For those who wants to use this instead of other methods. I pushed an npm package too.
material-ui-styled-hook

Inspect widget font, margin, padding and other properties

Using flutter inspector I'm not able to find a list of widget's properties like font size, font family, border color, padding, margin ...
For example, in a Text widget the available properties are textDirection, textAlign, size, and a few more, like in screenshot below:
I would like to see all computed properties, like browsers do:
Of course they are different tools, but i would like to know if there is a way to check more properties.
I also found a code-based solution, to access widget properties after it has been rendered, like in this question:
How to get the TextFormField text color,fontsize, etc using controller in flutter?
Is that the best way to achieve the result? Is it possible to use Keys in a way which allows to access widget properties without using a custom widget (i would like to save the reference to a Text, Row, etc. widgets and print all their properties)?
I also tried to use toDiagnosticsNode and toStringDeep methods as follows (_textWidget is of course a Text widget created above, this code is called when i click a button):
var test = _textWidget.toDiagnosticsNode().getProperties();
if (test != null && test.isNotEmpty) {
test.forEach((element) {
print(element.toStringDeep());
});
}
Is it possible to play a bit with a recursive function to get all the properties? All i can see with that code is the text displayed.

Flutter 'Flip Card' randomly pick a different picture on each flip

I am creating a Flutter app using the Flip Card Package. I would like to be able to flip a card and see a randomly selected image. Example: Flash cards, one side is a static image, I flip it to see a picture of a dog. I then tap again and it flips to back to the static image. I tap again, and I see a picture of a cat, etc...
I was able to get it all set up. The card flips back and forth but it is not randomly selecting a new card unless I restart the build after each flip. So, I am missing a piece somewhere that tells the app to pick again. Your help would be super appreciated!
Flip Card description https://pub.dev/packages/flip_card
snippet of code
Just as a tip as it seems you are new to SO - please post the code as text so that we can paste is in an IDE and try to run it. As an image its much harder to replicate your issue. Just a tip for going forward. Otherwise, welcome to SO!
So for your issue, I am not certain exactly why since I cannot see the entire context within that snippet BUT i suspect its because the card doesn't know it needs to rebuild since its just being replaced by another image but it doesn't know its a new image.
You can create a Widget class thats stateless and pass in the path. The Key change will ensure that the widget gets rebuilt:
The Widget class:
class FlipCardDetails extends StatelessWidget {
final String imagePath;
const FlipCardDetails({Key key, #required this.imagePath}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Stack(
children: <Widget>[
Image.asset(
this.imagePath,
key: ValueKey<String>(imagePath),
)
],
)
);
}
}
Then in your main code where it says:
back: Container(child: _cardDetails()),
You can replace this with:
back: FlipCardDetails(imagePath: <Provide the image path>);
You can create a key based on the image path itself like so:
key: ValueKey<String>(imagePath)
This all would only work if there is a function you can run when the person clicks the card that would flip the card. That way you can run your randomizer and provide the image path to the widget.
Instead if you can't and the widget needs to calculate its own random path, then make it stateful, and on initState change the image path. That should work as well.
Without more context I can't really be sure if this is right or which solution works better. Let me know and I can to advise more.
I think that the problem is that your calling the 'Random' function only once when initializing your card. Try adding a function in which you call the Random function to change your '_random' and your 'element' and call it every time the card is flipped.You can do that by using the 'onFlipDone' function field of the FlipCard.

GWT Widget not properly set in the DOM

I would like to print a GWT widget which extends Composite. This widget is composed of a grid whose cells are built with a ListDataProvider. When the user clic on a button print, the widget to print is built. Once this is done, I launch the print:
Element element = widgetToPrint.getElement();
String content = element.getInnerHTML();
print(content);
public static native boolean print(String content)
/*-{
var mywindow = window.open('', 'Printing', '');
mywindow.document.write('<html><head><title>Test</title>');
mywindow.document.write('<link rel="stylesheet" href="/public/stylesheets/ToPrintWidget.css" type="text/css" media="all"/></head><body>');
mywindow.document.write(content);
mywindow.document.write('</body></html>');
mywindow.print();
return true;
}-*/;
So, here is my problem:
The window which is opened by this method contains the core of the widget (built by the UI Binder), but some children are missing...
If I look inside the ListDataProvider and its related FlowPanel, the data are consistent, i.e. I've got several item in my list and in the flowPanel.
Consequently, it should be visible on the printing window...
I thought that maybe the problem was related to the method used to print the widget, so I also tried to add this widget into a dialogbox just before launching the print, to see if the widget was properly built... and it was.
So my widget displays well on a dialogbox, but if I try to give its innerHTML to the print method, by using getElement(), some widgets are missing... I've the feeling that the widgets which should have been built when the ListDataProvider changes are not properly set in the DOM... Somehow it works when I add the widget to a regular component, but it doesn't work when I have to give directly its innerHTML...
Do you have any idea ?
Thanks in advance.
Widgets are not just the sum of their elements, and DOM elements are not just the string that they are serialized to. Widgets are the element, and all events sunk to the dom to listen for any changes or interactions by the user. Elements then have callback functions or handlers they invoke when the user interacts with them.
By serializing the element (i.e. invoking getInnerHTML()), you are only reading out the structure of the dom, not the callbacks, and additionally not the styles set by CSS. This probably shouldn't be expected to work correctly, and as your experience is demonstrating, it doesn't.
As this is just a print window you are trying to create, event handling is probably not a concern. You just want the ability to see, but not interact with, the content that would be in that set of widgets. Styles are probably the main problem here then (though your question doesn't specify 'some children are missing' doesn't tell us what is missing, or give us any more clues as to why...) - you are adding one stylesheet in your JSNI code, but CellTable (which I assume you are using since you reference ListDataProvider) needs additional CssResource instances to appear correctly. I'm not sure how you can hijack those to draw in a new window.
Are you only using this to print content, not to let the user directly interact with the data? If so, consider another approach - use a SafeHtmlBuilder to create a giant, properly escaped string of content to draw in the new window.
String content = element.toString();
This will include all hierarchy elements in the node.
Just a reminder, all the GWT handlers will not work, and you have to sink all the events using DOM.
You might want to grab the outer HTML rather than the inner one.
GWT unfortunately has no getOuterHTML, but it's relatively easy to emulate.
If your widget is the only child within an element, then simply get the inner HTML of the parent element (w.getElement().getParentElement().getInnerHTML())
Otherwise, clone your widget's node add it to a newly created parent element, from which you'll be able to get the inner HTML:
DivElement temp = Document.get().createDivElement();
temp.appendChild(w.getElement().cloneNode(true));
return temp.getInnerHTML();
First thank you for your answers, it helped me to work out this problem.
I've almost solve the problem:
First, I do not use ListDataProvider anymore, because it wasn't clear for me when and how the view was refreshed. Instead I add my widgets by hand, which makes sense since, they are not going to move anyway.
Then, I define the style of my widgets using a common CSS stylesheet. However, in order to do it, I can't rely on CssResource, which was the way I was used to do it with GWT. I think that this comes from the JS method which gets lost by this kind of styles... Instead, I have to specify everything in a static CSS stylesheet, and to give it to the JS.
It works perfectly well, ie, I have my widgets, with thei styles, and I can print it.
But...
The color of some widgets depends on the color of the object that they represent. Consequently, I cannot write a generic CSS stylesheet... And as I said, I can't add a style using CssResource... Do you have any ideas on the way to handle that ?
To make sure I'm clear on the way I'm adding styles, here is an example:
Label l = new Label("Here is a cell in my grid to be printed");
l.addStyleName("PrintLineCell-kind_1");
With, in a public CSS stylesheet:
.PrintLineCell-kind_1{
background-color: red;
}
I hope there is a better way than to write 300 styles to cover 300 different colors...