# Java FX: Mathematik Editor



## Kababär (13. Jan 2017)

Hi,

Ich würde gerne einen mathematischen Texteditor schreiben, mit dem es möglich ist mathematische und logische Formeln zu schreiben und das möglichst auf eine einfache Art und Weise.  
Wichtig ist mir hierbei, dass Buchstaben und Formeln  hoch und tiefgestellt werden können, ausserdem wäre es praktisch, wenn griechische Buchstaben unterstützt werden sowie die gängigen Zeichenformeln wie Sigma (Summenformel), kanonsche Summenformel, Integrale, etc.

Wie würdet ihr da am besten vorgehen? Also welches Pane würdet ihr da empfehlen, etwa Canvas? 
Oder wäre Javascript eine Lösung, so dass ich quasi den Texteditor als Webscript in einer Webview anzeige lasse?

Hintergrund hierfür ist, dass mir OpenOffice und LibreOffice nicht so gefallen wenn es darum geht, komplizierte Brüche etc zu schreiben. 
Wenn das Programm fertig ist, würde ich es gerne erweitern um algebraische Umformungen zu machen, DGLs zu lösen, etc. 
Einfach um mich wieder etwas mehr mit der Mathematik und gleichzeitig mit Java zu beschäftigen. 

Da ich auch gerade Python lerne.. seht ihr eine Möglichkeit Python sinnvoll einzusetzen? Python hat ja tolle packages wie numpy und scypy. 

Über antworten würde ich mich freuen.


----------



## dzim (16. Jan 2017)

Aus dem Stehgreif fällt mir nichts brauchbares ein. Aber du kannst folgendermassen vorgehen:

Python in Java -> Jython 
http://www.jython.org/ 
Leider nur Python 2.7 und das obwohl das bald deprecated/EOL wird...
(Für Tutorials zur Integration mal googlen. Ist im Wesentlichen ein Python-Interpreter auf der JVM, und vermutlich durch die ScriptEngine der JVM angebunden. Hab sie selbst - trotz Interesse - nie verwendet.)

Für die Formeln wird es etwas schwerer: Ich habe mal nach "javafx formula" gegooglet und bin dabei u.a. auf folgende Links gestossen

http://stackoverflow.com/questions/24973389/display-math-formula-in-javafx
https://github.com/bitstormGER/jlatexmathfx
Du kannst/musst also deine Formeln in einer Beschreibung wie etwa LaTeX machen und dann durch die Controls in den Links in deiner Anwendung rendern lassen.


----------



## Kababär (16. Jan 2017)

Ja so weit bin ich auch mittlerweile auch schon.
Habe bisher alles in JavaFX realisiert ohne Jython (da es mit Python 2.x doch etwas umständlicher ist als ich dachte).
Problem sind nur die JavaFX Controls.. weshalb ich wohl alles in Python machen werden.
Denn: LaTeX-, ich nenne ich sie mal Formulare, müssen als Bilder gerendered werden und JavaFX unterstützt bisher (soweit ich weiß) kein Textfeld, das Bilder und Texte akzeptiert.
Mit TextFlow habe ich es schon probiert und bekomme auch die passenden mathematischen Ausdrücke mittels LaTeX-Parser als Image, welches ich in TextFlow einfüge. 
Problem: Das Ganze soll ja ein Editor werden und ich habe bisher noch keine Möglichkeit gefunden, während der Laufzeit in ein Textflow zu schreiben, da ja alles Text und nicht als String dort gespeichert wird und ein Textflow nicht direkt editierbar ist. So müsste ich alles über ein Eingabefeld machen, was mMn. aber Humbug ist (für die Formeln wäre das ok...).
Das Problem ist also kurz zusammengefasst: Mir fehlt ein Control, dass wie eine TextArea ist aber zusätzlich noch Bilder akzeptiert (quasi eine Kombi aus TextArea und TextFlow).

Pythons TKinter TextBox unterstützt das Anzeigen von Text und Bild und funktioniert so ähnlich wie eine JavaFX TextArea.

PS: Habe auch schon den Weg über WebView + JavaScript + HTML versucht, doch auch dort habe ich das Problem, dass die TextArea kein Bild einfügen kann (zumindest hat es bei mir nicht geklappt).


----------



## dzim (16. Jan 2017)

Verstehe die Problematik. Ich würde in dem Fall trotzdem einen Textflow nehmen. Die fertigen Ausdrücke (nach Eingabetaste oder so?) anzeigen. Und die Eingabe selbst: Ich würde einfach als letzes Element im TextFlow ein TextField einfügen. Alternativ ginge auch ein FlowPane, dann wird immer alles ordentlich zentriert dargestellt (das ist bei TextFlow einfach etwas kniffelig). Und ich würde den Container (FlowPane oder TextFlow) im CSS so gestalten, dass es wie eine Text-Area aussieht und dass TextField ohne Rahmen (damit man nicht sieht, dass du schummelst ).
Ich kenn mich nicht mehr so genau mit der LaTeX-Syntax aus (Open-/LibreOffice hat(te) ja IMHO ein vereinfachtes Derivat davon in Verwendung), aber wenn du mir ein paar Formeln gibst, würde ich vielleicht damit mal ein kleines Beispiel machen und hier posten (du kannst ja dein Python-Programm weitermachen).


----------



## Kababär (16. Jan 2017)

Also bei OpenOffice/LibreOffice finde ich es einfach sehr unpraktisch, dass man immer diesen Mathe-Editor aufmachen muss und er sich immer schließt, sobald man eine Formel eingegeben hat.
Deswegen würde ich gerne diesen Workflow beschleunigen.

So in etwa dachte ich mir das auch mit dem TextField einbinden nach jeder Space-Taste, aber das Alignment-Management wäre mir dann einfach etwas zu heftig und kompliziert bei dem TextFlow.. Mit CSS kenne ich mich noch nicht so gut aus, vor allem wenn es darum geht Controls stark abzuändern.
Bisher habe ich immer nur Textfarben oder ähnliches verändert aber nie die Form. Denke da kann ich mich aber auch leicht einarbeiten (falls es gut dokumentiert ist).

Falls du wirklich Interesse hast:
Die Syntax von LaTeX ist denkbar einfach und leicht zu merken (zumindest für mathematische Ausdrücke).
Hier ist mal eine schnelle Referenz:
http://meta.math.stackexchange.com/questions/5020/mathjax-basic-tutorial-and-quick-reference
Prinzipiell benötigt man (zumindest ich in diesem Fall) nur folgende Ausdrücke:

Tiefgestelle Ausdrücke: "_" -> x_0 ergibt ein x mit einer tiefgestellten 0. Allgemeine Syntax lautet _{} wobei man in der geschweiften Klammer mehrere Ausdrücke angeben kann
Hochgestellte Ausdrücke: ^{} -> x^0 ergibt ein x mit einer hochgestellten 0.
Integral: \int_{}^{}: ergibt ein Integral mit einer von (underscore) bis (hochgestellt) Grenze
Summe: \sum_{}^{}: ergibt das Summenzeichen mit den Grenzen, Beispiel: \sum_{i=0}^{n}
Brüche: \frac{}{}: erster Ausdruck steht für Zähler, zweiter für Nenner, Beispiel: \frac{\cos{\theta}}{\sin{\alpha}}
Griechische Buchstaben: einfach ein vorangestelltes "\" und den Namen, Beispiel: \alpha, \beta\, \delta, \Delta, \theta, ...
Da gibt's noch was zu beachten:
Es gibt sogenannte Inline und Display Methoden:
Inline-Methoden werden benutzt, wenn man einen mathematischen Ausdruck innerhalb eines Textes anzeigen will, während der Display-Modus quasi als Bild dargestellt wird, das in eine eigene Zeile geschrieben wird und auch eine ganze Zeile beansprucht.
Für Inline umschließt man den gesamten Ausdruck typischerweise mit: $ausdruck$
Für Display kann man entweder "$$ausdruck$$" oder "\[ausdruck\]" verwenden.

Im Folgenden zeige ich dir mal bisher, was ich so habe:


Spoiler: MainApp





```
public class MainApp extends Application {

    @Override
    public void start(Stage stage) {
        MainPresenter mainPresenter = initApplication();
        Scene scene = new Scene(mainPresenter.getView(), 600, 400);
        stage.setScene(scene);
        stage.setTitle("Math Editor - Simplyfy");
        stage.show();
    }

    private MainPresenter initApplication() {
        MainPresenter mainPresenter = new MainPresenter();
        MyEditorView mainView = new MyEditorView();
        FormulaModel model = new FormulaModel();

        mainView.setPresenter(mainPresenter);
        mainPresenter.setView(mainView);
        mainPresenter.setFormulaModel(model);
        mainView.init();

        return mainPresenter;
    }

    public static void main(String[] args) {
        launch(args);
    }
}
```






Spoiler: Presenter





```
public class MainPresenter {

    private MyEditorView view;
    private FormulaModel model;

    public void setFormulaModel(FormulaModel mod) {
        this.model = mod;
    }

    public void updateFormulaModel(ImageView imageView, String formula) {
        if ((model.getOriginalStringFromImage(imageView) == null)
                || model.getOriginalStringFromImage(imageView).isEmpty()) {

        } else {
            model.addImageAndFormula(imageView, formula);
        }
    }

    public String getFormulaFromSelectedImageView(ImageView iv) {
        return model.getOriginalStringFromImage(iv);
    }

    public ImageView createImageViewFromFormula(String formula) {

        ImageView iv = null;
        if (TeXUtil.isParsable(formula)) {
            iv = TeXUtil.parseFormula(formula);
            model.addImageAndFormula(iv, formula);
        }

        return iv;
    }

    public void removeFormula(ImageView iv) {
        model.getMap().remove(iv);
    }

    public void setView(MyEditorView mainV) {
        this.view = mainV;
    }

    public MyEditorView getView() {
        return this.view;
    }
}
```






Spoiler: Model





```
import java.util.LinkedHashMap;

import javafx.scene.image.ImageView;

public class FormulaModel {

    private LinkedHashMap<ImageView, String> imageFormula;

    public FormulaModel() {
        this.imageFormula = new LinkedHashMap<>();

    }

    public LinkedHashMap<ImageView, String> getMap() {
        return this.imageFormula;
    }

    public void addImageAndFormula(ImageView imgView, String formula) {
        this.imageFormula.put(imgView, formula);
    }

    public String getOriginalStringFromImage(ImageView img) {
        if (img == null) {
            return new String();
        }
        return this.imageFormula.get(img);
    }
}
```






Spoiler: View





```
public class MyEditorView extends VBox {

    private MainPresenter presenter;
    private ImageView selectedImageView;

    public MyEditorView() {
    }

    public void init() {

        MyTextFlow textFlow = new MyTextFlow();
        textFlow.setLineSpacing(10);
        textFlow.setPadding(new Insets(10));

        TextField textField = new TextField();
        Button button = new Button("Send");
        button.setPrefWidth(70);

        getChildren().addAll(textFlow, new HBox(textField, button));
        setVgrow(textFlow, Priority.ALWAYS);

        // Textfield re-sizes according to VBox
        textField.prefWidthProperty().bind(this.widthProperty().subtract(button.prefWidthProperty()));

        // On Enter press
        textField.setOnKeyPressed(e -> {
            if (e.getCode() == KeyCode.ENTER) {
                button.fire();
            }
        });

        button.setOnAction(e -> {
            if (selectedImageView == null) {
                String frml = textField.getText();
                ImageView iv = presenter.createImageViewFromFormula(frml);
                if (iv != null) {
                    textFlow.getChildren().add(iv);
                    selectedImageView = iv;
                } else {
                    textField.setText(textField.getText() + " could not be parsed!");
                }
            } else {
                if (textField.getText().isEmpty()) {
                    presenter.removeFormula(selectedImageView);
                } else {
                    presenter.updateFormulaModel(selectedImageView, textField.getText());
                    ImageView tmpIv = presenter.createImageViewFromFormula(textField.getText());
                    if (tmpIv != null) {
                        Image img = tmpIv.getImage();
                        selectedImageView.setImage(img);
                    } else {
                    }
                }
            }

        });

        String formula = "\\sum_{i=0}^n i^2 = \\frac{(n^2+n)(2n+1)}{6}";

        ImageView iv = presenter.createImageViewFromFormula(formula);
        textFlow.getChildren().add(iv);
        presenter.updateFormulaModel(iv, formula);

        iv.setOnMouseClicked(e -> {
            String frml = presenter.getFormulaFromSelectedImageView(iv);
            if ((frml != null) && !frml.isEmpty()) {
                textField.setText(frml);
                selectedImageView = iv;
            }
        });
    }

    public void setPresenter(MainPresenter present) {
        this.presenter = present;
    }
}
```






Spoiler: TeXUtil





```
import java.awt.image.BufferedImage;

import org.scilab.forge.jlatexmath.TeXConstants;
import org.scilab.forge.jlatexmath.TeXFormula;

import javafx.embed.swing.SwingFXUtils;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

public class TeXUtil {

    public static boolean isParsable(String formula) {

        try {
            TeXFormula tex = new TeXFormula(formula);

            java.awt.Image awtImage = tex.createBufferedImage(TeXConstants.STYLE_DISPLAY, 12, java.awt.Color.BLACK,
                    null);
            SwingFXUtils.toFXImage((BufferedImage) awtImage, null);
        } catch (Exception e) {
            System.out.println("Could not create TeXFormula: " + e);
            return false;
        }
        return true;
    }

    public static ImageView parseFormula(String formula) {
        ImageView iv = null;
        try {
            TeXFormula tex = new TeXFormula(formula);

            java.awt.Image awtImage = tex.createBufferedImage(TeXConstants.STYLE_DISPLAY, 12, java.awt.Color.BLACK,
                    null);
            Image fxImage = SwingFXUtils.toFXImage((BufferedImage) awtImage, null);
            iv = new ImageView(fxImage);
            // As we create a new object which doesn't exist before, we need to
            // add
            // it directly to call it afterwards.
        } catch (Exception e) {
            System.out.println("Could not create TeXFormula: " + e);
        }

        return iv;
    }
}
```






Spoiler: ModifiedTextFlow





```
import home.TextForms.util.TeXUtil;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;

public class MyTextFlow extends TextFlow {

    public MyTextFlow() {
        super();
    }

    public void setText(String text) {
        String[] split = text.split("\\s+");
        getChildren().clear();
        Text t = new Text();
        StringBuilder sb = new StringBuilder();
        for (String str : split) {
            if (TeXUtil.isParsable(str)) {
                t.setText(sb.toString());
                getChildren().add(t);
                t = new Text();
                // reset StringBuilder
                sb.setLength(0); // set length of buffer to 0
                sb.trimToSize();

            } else {

                sb.append(str);
                sb.append(" ");
            }
        }
    }
}
```




Das Projekt ist ein Maven Projekt und meine POM.XML hat folgende Dependencies für die LaTeX-Lib:

```
<!-- https://mvnrepository.com/artifact/org.scilab.forge/jlatexmath -->
        <dependency>
            <groupId>org.scilab.forge</groupId>
            <artifactId>jlatexmath</artifactId>
            <version>1.0.4</version>
        </dependency>
```
Genutzt wird Java 8.
Die eigene TextFlow ist unfertig. Hier wollte ich die Idee mit dem "splitten" des Textes realisieren, aber hab nicht viel gemacht, weil mir relativ früh klar wurde, dass mein Vorhaben so nicht funktioniert.
Du kannst dir gerne nehmen was du brauchst.
Falls du alle Klassen importiert und das Programm startest, kannst du mal auf die Gleichung linksklicken, dann erscheint im TextFeld die LaTeX-Formel dafür und kannst etwas damit rumspielen, wird automatisch nach einem Enter-Klick oder Button-Klick aktualisiert.

Hoffe ich hab an alles gedacht 

Edit: Das Projekt ist mit Absicht nach dem MVP-Prinzip gestaltet, damit ich für die kommende Klausur besser vorbereitet bin (unser Dozent gibt das MVP-Prinzip vor). Normalerweise arbeite ich mit FXML und nach dem MVC-Prinzip. Hoffe dir macht das nichts aus.


----------



## dzim (17. Jan 2017)

Mir macht das nichts aus. Nur ich werde es wieder auf MVC umstellen, weil ich MVP etwas umständlich finde. (Siehe Verlinke Model auf Presenter, verlinke Presenter auf Model - und mach noch das Init auf dem View alleine (um so was kümmert sich doch der FXMLLoader )).
Ausserdem mag ich es nicht so sehr, die UI in Code zu schreiben.


----------



## Kababär (17. Jan 2017)

Ja das ist auch meine bevorzugte Art und Weise aber naja  
Richtig hässlich wird es wenn man mehrere Views hat. Ein reines Presenter - View- Model Gewurstel


----------



## dzim (17. Jan 2017)

Eben. Dann entzieht sich mir auch der Sinn des ganzen. Nur um MVP zu machen, muss ich mir doch nicht so einen sch*** antun.  Schon mal JavaFXPorts versucht eine App zu schreiben? Sie geben auch gern MVP vor, was ich auch dort nicht leiden kann...


----------



## Kababär (17. Jan 2017)

Das Argument meines Dozenten ist, dass MVP intuitiver und besser austauschbar wäre.

Ne das Vergnügen hatte ich bisher noch nicht


----------



## dzim (17. Jan 2017)

Hat dein Dozent schon überhaupt mal mit JavaFX gearbeitet? Klingt nach einem, der bei Swing geblieben ist. 
Wie auch immer: Das Gute ist ja - auch bei JavaFXPorts - dass man am Ende ja zu nichts gezwungen wird. Und auch, wenn du deinen Dozenten hinter dich gebracht hast, wird es so sein. Am Ende macht man das, was einem am Besten liegt. Es ist auf jeden Fall mal gut, dass du dadurch alles mögliche probieren konntest. In meiner Uni wurde man zu nichts gezwungen - solche Programmier-Paradigmen standen nur am Rande auf dem Programm.


----------



## Kababär (17. Jan 2017)

Ja sicher, wir behandeln nur Javafx.  Habe noch nie mit anderen Technologien gearbeitet (also weder Swing, AWT oder SWT). 
Wir kriegen ein Prinzip für die Klausur vorgeschrieben, selbstverständlich MVP, und es gibt massig Punktabzüge wenn wir es nicht mit MVP machen 
Auf der Arbeit programmiere ich auch mit MAC, gefällt mir halt am besten. 

Das mit dem "Man macht was einem am besten liegt" ist eine gute Sache.  Deswegen will ich auch so viel lernen wie es geht  

Sag mal: um mein Vorhaben zu realisieren mit dem Textflow; wäre es generell möglich ein Textflow so zu erweitern, dass man in das Control klicken kann und reinschreiben kann wie bei einem Textarea (ohne Verwendung anderer Controls, also ohne die Schummelei)? Ich würde mal gerne wissen wo die Grenzen liegen, Controls zu erweitern.


----------



## dzim (18. Jan 2017)

Nein, das dürfte nicht gehen. Immerhin ist ein TextFlow am Ende nichts anderes als ein Layout (erbt von Pane) und kein Control. Sicher: Em Ende erben alle von Node, aber das hat damit eher weniger zu tun.
Also: Entweder du schreibst dein eigenes Control von anfang an, inkl. Layouting und Text-Input, oder du kombinierst halt bestehende Sachen, was am Ende a) meist genügt und b) am schnellsten geht.
Ausserdem muss man sich sonst halt mit den JavaFX Skins beschäftigen - das war mir meist zu mühsam, daher habe ich mich bisher auf die Variante des Kombinierens verlassen.


----------



## dzim (18. Jan 2017)

Ich muss noch mal folgendes anmerken: Ich habe mal etwas mit der SciLab-Bibliothek herumgespielt und es funktioniert erwartungsgemäß.
Aber: Was genau ist das Ziel? Wie soll es am Ende aussehen?

Ich bin an dem Punkt, an dem ich einen Flow nicht mehr so toll finde: Zum einen muss man die Bilder eigentlich immer translaten (etwas nach unten bringen), damit es gut aussieht. Zum anderen ist ein Zeilenumbruch etwas aufwändiger:
Ich dachte erst, ein FlowPane wäre toll (die Bilder werden schön pro Zeile vertikal zentriert ausgelegt), aber Zeilenumbruch? Pustekuchen.
TextFlow: Zeilenumbruch möglich, aber dafür sind die items schlecht ausgelegt: text und input zentrieren sich, die Bilder aber nicht.
Ich überlege gerade, ob man einen ListView verwenden könnte: Eine Zeile = ein Listeneintrag.

Ansonsten würde ich wohl fast folgendes Vorgehen bevorzugen:
ScrollPane mit VBox, Und ein VBox-Kind = eine Formel. (Alternativ stat ScrollPane und VBox auch ListView).
Speziell zum ListView könnte mir hier ein Master-Detail-Modell vorstellen: Links die Liste mit den Einträgen und recht - nachdem sie angeklickt wurden - eine TextArea. Alternativ statt Detail-Bereich ginge auch ein Popup/Dialog für den Listeneintrag. Könnte mir hier einige Sachen vorstellen, aber als InlineEditor bin ich gerade mit den Styling auch etwas überfragt, solange wie wir nicht auf ein eigenes Pane (wo man sich eben um das Layouting selbst kümmern muss) zurückgreifen.

*#edit:* wenn du magst, kann ich das ja mal das, was ich hab, nach GitHub pushen und du schaust drüber - eventuell als "Collaborator" oder Forken und mit Pull Request updaten.


----------



## Kababär (19. Jan 2017)

Ok ich verstehe die Problematik.. hab gerade etwas wenig Luft zum schreiben. Daher würde ich sagen ich melde mich morgen oder übermorgen.  Nur damit du nicht denkst ich würde jetzt nicht mehr schreiben 

Aber:
Java FX ist ja schon mächtig.  Aber dass es nicht mal eine Textarea gibt, die Bilder akzeptiert  könnte doch alles so einfach sein.  

Was ist mein Ziel: wenn man es genau nimmt quasi ein LaTeX-Editor, der, wenn der User will, mathematische Formeln (in latex geschrieben) direkt umwandelt und anschaulich darstellt.  Da es derzeit etwas schnell gehen muss, verwende ich Texmaker (ein Latex Editor) um Zusammenfassungen für Module wie Angewandte Mathematik, Physik, Theoretische Informatik, etc zu schreiben.
Letzten Endes soll es ein Texteditor werden, der gängige Funktionen wie Zentrieren, dick/kursiv schreiben, etc anbietet wobei das Augenmerk auf das Schreiben von mathematischen Ausdrücken liegt, da die oft gebraucht werden.

Was ich mir auch überlegt habe:
Wie wäre es, wenn ich eigene Controls pro mathematischen Ausdruck schreibe? Bspw 4 HBoxes die jeweils ein TextField enthalten für das Summenzeichen. Eins für das zeigen an sich, eins für die obere, eins für die untere Grenze und eins für den nachfolgenden Ausdruck?

Gern kannst du auch mal das was du hast auf github stellen 

Edit: wobei bei meinem Vorschlag auch das Handling des Layouts wieder fragen aufwirft.  Kann man dazu nicht "einfach" die HBoxes relativ über die BoundsProperty abhängig von der (später gewählten) Schriftgröße zueinander setzen? Sorgen würde mir hier ganz klar die Performance machen und das würde auch den Rahmen des RAMs sprengen für einen einfachen Texteditor denke ich mal, wenn in dem Pane extrem viele Nodes wären. Komme mir gerade vor als ob ich versuche Tetris zu spielen 

Ich versuche mal mit deinen Vorschlägen etwas rumzuspielen und gucke mal wie weit ich komme bzw wie weit du gekommen bist. Ich danke dir jetzt schon für deine mühen


----------



## dzim (19. Jan 2017)

So. Hier mal meine geistigen Ergüsse in Git-Form.
Die GUI findest du hier:
https://github.com/bgmf/poc/tree/master/simple-tests-fx/src/main/resources/fxml/math
Und die Controller - und den gesamten Java-Teil - hier:
https://github.com/bgmf/poc/tree/master/simple-tests-fx/src/main/java/eu/dzim/tests/fx/math

Alles ist Teil meines ab und an mal aktualisierten Proof-of-Concept Repos. 
https://github.com/bgmf/poc

Wirklich viel mach ich nicht, aber ab und an ist mir etwas Wert es ausgelagert zu sichern (meine alten Repos sind z.T. eher nur noch antiquirte Stücke teils hässlichen Codes!).

Grüsse,
Daniel

PS: Um einen vollen Editor zu machen, muss man aber weiter weg schauen. Da reicht jlatexmath nicht mehr. Vielleicht snuggletex?


----------

