├── .github
├── FUNDING.yml
└── workflows
│ ├── fastlane.yml
│ ├── npm_tests.yml
│ └── main.yml
├── fastlane
└── metadata
│ └── android
│ ├── de-DE
│ ├── title.txt
│ ├── changelog
│ │ ├── 10700.txt
│ │ ├── 11300.txt
│ │ ├── 11100.txt
│ │ ├── 10900.txt
│ │ ├── 10902.txt
│ │ ├── 10800.txt
│ │ ├── 11000.txt
│ │ └── 11200.txt
│ ├── short_description.txt
│ ├── images
│ │ ├── icon.png
│ │ ├── featureGraphic.png
│ │ └── phoneScreenshots
│ │ │ ├── 1.jpg
│ │ │ ├── 2.jpg
│ │ │ └── 3.jpg
│ └── full_description.txt
│ └── en-US
│ ├── title.txt
│ ├── changelog
│ ├── 10700.txt
│ ├── 11300.txt
│ ├── 11100.txt
│ ├── 10900.txt
│ ├── 10902.txt
│ ├── 10800.txt
│ ├── 11000.txt
│ └── 11200.txt
│ ├── short_description.txt
│ ├── images
│ ├── icon.png
│ ├── featureGraphic.png
│ └── phoneScreenshots
│ │ ├── 1.jpg
│ │ ├── 2.jpg
│ │ └── 3.jpg
│ └── full_description.txt
├── www
├── img
│ ├── flatpak-icons
│ │ └── icon.png
│ ├── android-icons
│ │ ├── drawable-hdpi-icon.png
│ │ ├── drawable-ldpi-icon.png
│ │ ├── drawable-mdpi-icon.png
│ │ ├── drawable-xhdpi-icon.png
│ │ ├── drawable-xxhdpi-icon.png
│ │ └── drawable-xxxhdpi-icon.png
│ ├── donate.svg
│ └── gui
│ │ └── Classic_by_Joris Yidong Scholl.json
├── fonts
│ ├── Schulrechner-Italic.ttf
│ ├── Schulrechner-Regular.ttf
│ └── Schulrechner-Italic.sfd
├── index.html
├── css
│ └── styles.css
└── js
│ └── main.js
├── flatpak
├── io.github.CardiJey.schulrechner.png
├── io.github.CardiJey.schulrechner.desktop
└── io.github.CardiJey.schulrechner.metainfo.xml
├── schulrechner-cordova-plugin
├── package.json
├── plugin.xml
└── src
│ └── android
│ └── SchulrechnerPlugin.java
├── renovate.json
├── .gitignore
├── other
└── browser_redirection.html
├── res
└── icon
│ └── android
│ ├── background.xml
│ ├── background.svg
│ ├── monochrome.xml
│ ├── monochrome.svg
│ ├── splash.xml
│ ├── foreground.xml
│ └── foreground.svg
├── .gitmodules
├── tests
├── manual_test_results.csv
├── manual.js
└── svg.js
├── main.js
├── package.json
├── NOTICE
├── update_version.js
├── config.xml
└── README.md
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | liberapay: CardiJey
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/title.txt:
--------------------------------------------------------------------------------
1 | Schulrechner
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/title.txt:
--------------------------------------------------------------------------------
1 | Schulrechner
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelog/10700.txt:
--------------------------------------------------------------------------------
1 | First F-Droid Release!
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/changelog/10700.txt:
--------------------------------------------------------------------------------
1 | Erster F-Droid Release!
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/short_description.txt:
--------------------------------------------------------------------------------
1 | The calculator you know from school
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/short_description.txt:
--------------------------------------------------------------------------------
1 | Dein alter Taschenrechner aus der Schulzeit
--------------------------------------------------------------------------------
/www/img/flatpak-icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CardiJey/schulrechner/HEAD/www/img/flatpak-icons/icon.png
--------------------------------------------------------------------------------
/www/fonts/Schulrechner-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CardiJey/schulrechner/HEAD/www/fonts/Schulrechner-Italic.ttf
--------------------------------------------------------------------------------
/www/fonts/Schulrechner-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CardiJey/schulrechner/HEAD/www/fonts/Schulrechner-Regular.ttf
--------------------------------------------------------------------------------
/flatpak/io.github.CardiJey.schulrechner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CardiJey/schulrechner/HEAD/flatpak/io.github.CardiJey.schulrechner.png
--------------------------------------------------------------------------------
/www/img/android-icons/drawable-hdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CardiJey/schulrechner/HEAD/www/img/android-icons/drawable-hdpi-icon.png
--------------------------------------------------------------------------------
/www/img/android-icons/drawable-ldpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CardiJey/schulrechner/HEAD/www/img/android-icons/drawable-ldpi-icon.png
--------------------------------------------------------------------------------
/www/img/android-icons/drawable-mdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CardiJey/schulrechner/HEAD/www/img/android-icons/drawable-mdpi-icon.png
--------------------------------------------------------------------------------
/www/img/android-icons/drawable-xhdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CardiJey/schulrechner/HEAD/www/img/android-icons/drawable-xhdpi-icon.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CardiJey/schulrechner/HEAD/fastlane/metadata/android/de-DE/images/icon.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CardiJey/schulrechner/HEAD/fastlane/metadata/android/en-US/images/icon.png
--------------------------------------------------------------------------------
/www/img/android-icons/drawable-xxhdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CardiJey/schulrechner/HEAD/www/img/android-icons/drawable-xxhdpi-icon.png
--------------------------------------------------------------------------------
/www/img/android-icons/drawable-xxxhdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CardiJey/schulrechner/HEAD/www/img/android-icons/drawable-xxxhdpi-icon.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/images/featureGraphic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CardiJey/schulrechner/HEAD/fastlane/metadata/android/de-DE/images/featureGraphic.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/featureGraphic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CardiJey/schulrechner/HEAD/fastlane/metadata/android/en-US/images/featureGraphic.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/images/phoneScreenshots/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CardiJey/schulrechner/HEAD/fastlane/metadata/android/de-DE/images/phoneScreenshots/1.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/images/phoneScreenshots/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CardiJey/schulrechner/HEAD/fastlane/metadata/android/de-DE/images/phoneScreenshots/2.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/images/phoneScreenshots/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CardiJey/schulrechner/HEAD/fastlane/metadata/android/de-DE/images/phoneScreenshots/3.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CardiJey/schulrechner/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CardiJey/schulrechner/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CardiJey/schulrechner/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelog/11300.txt:
--------------------------------------------------------------------------------
1 | Feature: °′‴-button now switches to DMS format
2 | Feature: Added option to toggle between Deg, Rad and Gra angle modes
3 | Tweak: Updated dependencies
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/changelog/11300.txt:
--------------------------------------------------------------------------------
1 | Feature: °′‴-Knopf zeigt Ergebnis nun im DMS format an
2 | Feature: Option hinzugefügt zwischen Deg, Rad and Gra Winkel Modus zu wählen
3 | Tweak: Dependencies aktualisiert
4 |
--------------------------------------------------------------------------------
/schulrechner-cordova-plugin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "schulrechner-cordova-plugin",
3 | "version": "1.0.0",
4 | "cordova": {
5 | "id": "schulrechner-cordova-plugin",
6 | "platforms": ["android"]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "config:recommended"
5 | ],
6 | "git-submodules": {
7 | "enabled": true
8 | },
9 | "automerge": true
10 | }
11 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelog/11100.txt:
--------------------------------------------------------------------------------
1 | Feature: Added (-) button
2 | Feature: Added keyboard support for desktop version
3 | Feature: Added hyperbolic functions
4 | Tweak: "Renovate" integrated to ensure up-to-date dependencies
5 | Tweak: Updated dependencies
6 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/changelog/11100.txt:
--------------------------------------------------------------------------------
1 | Feature: (-) hinzugefügt
2 | Feature: Tastatur-Support für die Desktop Version
3 | Feature: Hyperbelfunktionen hinzugefügt
4 | Tweak: "Renovate" integriert um Dependencies up-to-date zu halten
5 | Tweak: Dependencies aktualisiert
6 |
--------------------------------------------------------------------------------
/flatpak/io.github.CardiJey.schulrechner.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Version=1.0
3 | Type=Application
4 |
5 | Name=Schulrechner
6 | Comment=The calculator you know from school
7 | Categories=Education;Math;
8 |
9 | Icon=io.github.CardiJey.schulrechner
10 | Exec=run.sh
11 | Terminal=false
12 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelog/10900.txt:
--------------------------------------------------------------------------------
1 | Schulrechner now also available on Flathub!
2 | Bugfix: Output should no longer cut off
3 | Feature: Added settings menu with info links
4 | Feature: Added option to switch decimal format for other locales
5 | Feature: Added ASin, ACos and ATan
6 | Feature: Added M+ and M-
7 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelog/10902.txt:
--------------------------------------------------------------------------------
1 | Schulrechner now also available on Flathub!
2 | Bugfix: Output should no longer cut off
3 | Feature: Added settings menu with info links
4 | Feature: Added option to switch decimal format for other locales
5 | Feature: Added ASin, ACos and ATan
6 | Feature: Added M+ and M-
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | # Generated by package manager
4 | node_modules/
5 |
6 | # Generated by Cordova
7 | /plugins/
8 | /platforms/
9 |
10 | # Custom
11 | .flatpak-builder/
12 | dist/
13 | www/vendor
14 | *.jks
15 | *.b64
16 | temp/
17 | www/changelog
18 | www/versionCode.txt
19 | www/version.txt
20 | www/js/math.js
21 | www/img/gui/list.json
22 | .vscode/
23 |
--------------------------------------------------------------------------------
/other/browser_redirection.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redirecting...
7 |
8 |
9 | If you are not redirected automatically, click here .
10 |
11 |
12 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/changelog/10900.txt:
--------------------------------------------------------------------------------
1 | Schulrechner jetzt auch auf Flathub verfügbar!
2 | Bugfix: Die Ausgabe sollte nicht mehr abgeschnitten werden
3 | Feature: Einstellungsmenü mit Info-Links hinzugefügt
4 | Feature: Option zum Umschalten des Dezimalformats für andere Sprachräume hinzugefügt
5 | Feature: ASin, ACos und ATan hinzugefügt
6 | Feature: M+ und M- hinzugefügt
7 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/changelog/10902.txt:
--------------------------------------------------------------------------------
1 | Schulrechner jetzt auch auf Flathub verfügbar!
2 | Bugfix: Die Ausgabe sollte nicht mehr abgeschnitten werden
3 | Feature: Einstellungsmenü mit Info-Links hinzugefügt
4 | Feature: Option zum Umschalten des Dezimalformats für andere Sprachräume hinzugefügt
5 | Feature: ASin, ACos und ATan hinzugefügt
6 | Feature: M+ und M- hinzugefügt
7 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelog/10800.txt:
--------------------------------------------------------------------------------
1 | Feature: Added operations for cube and n-th root, cube power, factorial, and e to the power of n
2 | Feature: Operators now automatically insert the previous result at the beginning
3 | Feature: Added test-procedure during development, to ensure future updates won't break anything
4 | Tweak: Enlarged hitboxes for buttons. Easier to tap.
5 | Tweak: More readable colors
6 | Bugfixes: Complex results now produce an error
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelog/11000.txt:
--------------------------------------------------------------------------------
1 | Feature: Added option to switch between multiple designs (although currently only the default is available, more designs are coming!)
2 | Feature: Added constants (a way to view the list of constants is planned)
3 | Bugfix: Very large results no longer cause the app to hang
4 | Bugfix: Font scaling fixed, preventing cutoff and incorrect line wrapping
5 | Bugfix: Sums in trigonometric functions are now input correctly
6 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/changelog/10800.txt:
--------------------------------------------------------------------------------
1 | Feature: Operationen dritte und n-te Wurzel, hoch 3, Fakultät und e hoch n hinzugefügt
2 | Feature: Operatoren fügen an erster Stelle nun automatisch das vorherige Ergebnis ein
3 | Feature: Entwicklungsseitige Test-Prozedur eingeführt die verhindert das Updates etwas kaputt machen
4 | Tweak: Hitboxen für Knöpfe vergrößert. Tippen erleichtert.
5 | Tweak: Besser lesbare Farben
6 | Bugfixes: Komplexe Ergebnisse geben nun einen Fehler aus
--------------------------------------------------------------------------------
/res/icon/android/background.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "mathjs"]
2 | path = mathjs
3 | url = https://github.com/josdejong/mathjs.git
4 | branch = v15.1.0
5 | [submodule "cordova-plugin-inappbrowser"]
6 | path = cordova-plugin-inappbrowser
7 | url = https://github.com/apache/cordova-plugin-inappbrowser.git
8 | [submodule "cordova-plugin-android-remove-permissions"]
9 | path = cordova-plugin-android-remove-permissions
10 | url = https://github.com/globules-io/cordova-plugin-android-remove-permissions.git
11 |
--------------------------------------------------------------------------------
/tests/manual_test_results.csv:
--------------------------------------------------------------------------------
1 | versionCode,ui_loads_test,buttons_work_test,overflow_scrolling_x_test,overflow_scrolling_y_test,nested_fractions_display_test,brackets_display_test,sqrt_display_test,urls_work_phone,urls_work_desktop,hotkeys_work
2 | 10704,y,y,y,y,y,todo,todo,todo,todo,todo
3 | 10800,y,y,y,y,y,todo,todo,todo,todo,todo
4 | 10900,y,y,y,y,y,todo,todo,todo,todo,todo
5 | 11000,y,y,y,y,y,todo,todo,y,y,todo
6 | 11100,y,y,y,y,y,todo,todo,y,y,y
7 | 11200,y,y,y,y,y,todo,y,y,y,y
8 | 11300,y,y,y,y,y,todo,y,y,y,y
--------------------------------------------------------------------------------
/.github/workflows/fastlane.yml:
--------------------------------------------------------------------------------
1 | name: Check Fastlane
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 |
9 | jobs:
10 | validate-metadata:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout repository
14 | uses: actions/checkout@v6
15 |
16 | - name: Validate Fastlane Supply Metadata
17 | uses: ashutoshgngwr/validate-fastlane-supply-metadata@v2.1.0
18 | with:
19 | fastlaneDir: ./fastlane/metadata/android # optional
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelog/11200.txt:
--------------------------------------------------------------------------------
1 | Feature: Added config option to prefer decimal results
2 | Feature: Added mode menu
3 | Feature: Added CMPLX mode and i
4 | Feature: Added integration
5 | Feature: Added differentiation
6 | Feature: Added setup menu
7 | Feature: Added Norm and Fix rounding setup modes
8 | Feature: Added sexagesimal (hours, minutes, seconds) button
9 | Bugfix: Eliminated rounding errors in trigonometric functions
10 | Bugfix: Cube root of –1 now computes to –1
11 | Tweak: Updated dependencies
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/changelog/11000.txt:
--------------------------------------------------------------------------------
1 | Feature: Option zum Wechseln zwischen mehreren Designs hinzugefügt (derzeit ist nur das Standarddesign verfügbar, aber weitere Designs folgen!)
2 | Feature: Konstanten hinzugefügt (eine Möglichkeit zur Anzeige der Konstantenliste ist geplant)
3 | Bugfix: Sehr große Ergebnisse lassen die App nicht mehr hängen
4 | Bugfix: Schriftgrößenanpassung korrigiert, sodass kein Abschneiden oder falscher Zeilenumbruch mehr erfolgt
5 | Bugfix: Summen in trigonometrischen Funktionen werden jetzt korrekt berechnet
6 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/changelog/11200.txt:
--------------------------------------------------------------------------------
1 | Feature: Einstellung hinzugefügt, um Dezimalergebnisse zu bevorzugen
2 | Feature: Modus-Menü hinzugefügt
3 | Feature: CMPLX-Modus und i hinzugefügt
4 | Feature: Integration hinzugefügt
5 | Feature: Differentiation hinzugefügt
6 | Feature: Setup-Menü hinzugefügt
7 | Feature: Norm- und Fix-Rundungsmodi in den Einstellungen hinzugefügt
8 | Feature: Sexagesimal-Taste (Stunden, Minuten, Sekunden) hinzugefügt
9 | Bugfix: Rundungsfehler bei trigonometrischen Funktionen behoben
10 | Bugfix: Kubikwurzel von –1 ergibt nun –1
11 | Tweak: Dependencies aktualisiert
12 |
--------------------------------------------------------------------------------
/schulrechner-cordova-plugin/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | SchulrechnerPlugin
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | const { app, BrowserWindow } = require('electron');
2 | const path = require('path');
3 |
4 | let mainWindow;
5 |
6 | app.whenReady().then(() => {
7 | mainWindow = new BrowserWindow({
8 | height: 600,
9 | width: 300,
10 | minWidth: 300,
11 | minHeight: 600,
12 | webPreferences: {
13 | nodeIntegration: true, // Allow Node.js integration
14 | contextIsolation: false,
15 | }
16 | });
17 |
18 | if(app.isPackaged){
19 | const { Menu } = require('electron');
20 | Menu.setApplicationMenu(null);
21 | }
22 |
23 | mainWindow.setAspectRatio(1 / 2);
24 |
25 | mainWindow.loadFile(path.join(__dirname, 'www', 'index.html'));
26 |
27 | mainWindow.on('closed', () => {
28 | mainWindow = null;
29 | });
30 | });
31 |
32 | app.on('window-all-closed', () => {
33 | if (process.platform !== 'darwin') app.quit();
34 | });
35 |
--------------------------------------------------------------------------------
/.github/workflows/npm_tests.yml:
--------------------------------------------------------------------------------
1 | name: NPM Tests
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 |
9 | jobs:
10 | test-functionality:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - name: Checkout repo
15 | uses: actions/checkout@v6
16 | with:
17 | fetch-depth: 0 # Ensures full history is fetched
18 | fetch-tags: true
19 |
20 | - name: Set up Node.js
21 | uses: actions/setup-node@v6
22 | with:
23 | node-version: 24
24 |
25 | - name: Install dependencies
26 | run: |
27 | npm install
28 | npm install xml2js
29 |
30 | - name: Build and move math.js bundle
31 | run: |
32 | git submodule update --init --recursive
33 | cd mathjs
34 | npm install
35 | npm run build
36 | cd ..
37 | mv mathjs/lib/browser/math.js www/js/math.js
38 |
39 | - name: Run NPM Tests
40 | run: npm test
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "schulrechner",
3 | "displayName": "Schulrechner",
4 | "version": "1.13.0",
5 | "versionCode": 11200,
6 | "description": "The calculator you know from school",
7 | "main": "main.js",
8 | "scripts": {
9 | "test": "node --test tests/*.js",
10 | "update_version": "node update_version.js",
11 | "start": "electron .",
12 | "dist": "electron-builder"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "https://github.com/CardiJey/schulrechner"
17 | },
18 | "author": "Joris Yidong Scholl",
19 | "license": "GPL-3.0-only",
20 | "devDependencies": {
21 | "electron": "^39.0.0",
22 | "electron-builder": "^26.0.0"
23 | },
24 | "cordova": {
25 | "plugins": {
26 | "cordova-plugin-android-remove-permissions": {
27 | "locator": "cordova-plugin-android-remove-permissions"
28 | },
29 | "cordova-plugin-inappbrowser": {
30 | "locator": "cordova-plugin-inappbrowser"
31 | },
32 | "schulrechner-cordova-plugin": {
33 | "locator": "schulrechner-cordova-plugin"
34 | }
35 | },
36 | "platforms": [
37 | "android"
38 | ]
39 | },
40 | "dependencies": {
41 | "mathjs": "^15.0.0"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/schulrechner-cordova-plugin/src/android/SchulrechnerPlugin.java:
--------------------------------------------------------------------------------
1 | package com.schulrechner.plugin;
2 |
3 | import android.util.Log;
4 | import android.webkit.WebView;
5 | import android.webkit.WebSettings;
6 |
7 | import org.apache.cordova.CordovaPlugin;
8 | import org.apache.cordova.CordovaInterface;
9 | import org.apache.cordova.CordovaWebView;
10 |
11 | public class SchulrechnerPlugin extends CordovaPlugin {
12 | private static final String LOG_TAG = "SchulrechnerPlugin";
13 |
14 | @Override
15 | public void initialize(final CordovaInterface cordova, final CordovaWebView webView) {
16 | super.initialize(cordova, webView);
17 |
18 | cordova.getActivity().runOnUiThread(new Runnable() {
19 | @Override
20 | public void run() {
21 | try {
22 | WebView wv = (WebView) webView.getEngine().getView();
23 | WebSettings ws = wv.getSettings();
24 |
25 | Log.w(LOG_TAG, "[ResetFontSize][loaded] getTextZoom: " + ws.getTextZoom());
26 | ws.setTextZoom(100);
27 | Log.w(LOG_TAG, "[ResetFontSize][updated] getTextZoom: " + ws.getTextZoom());
28 | } catch (Exception e) {
29 | Log.e(LOG_TAG, "Error setting WebView text zoom", e);
30 | }
31 | }
32 | });
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/www/img/donate.svg:
--------------------------------------------------------------------------------
1 | Donate
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | # Copyright
2 |
3 | https://github.com/CardiJey/schulrechner
4 |
5 | Copyright (C) 2025 Joris Yidong Scholl
6 |
7 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
8 |
9 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
10 |
11 | You should have received a copy of the GNU General Public License along with this program. If not, see .
12 |
13 | # Dependencies
14 |
15 | This project uses math.js
16 | https://github.com/josdejong/mathjs
17 |
18 | Copyright (C) 2013-2025 Jos de Jong
19 |
20 | Licensed under the Apache License, Version 2.0 (the "License");
21 | you may not use this file except in compliance with the License.
22 | You may obtain a copy of the License at
23 |
24 | https://www.apache.org/licenses/LICENSE-2.0
25 |
26 | Unless required by applicable law or agreed to in writing, software
27 | distributed under the License is distributed on an "AS IS" BASIS,
28 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29 | See the License for the specific language governing permissions and
30 | limitations under the License.
31 |
--------------------------------------------------------------------------------
/www/img/gui/Classic_by_Joris Yidong Scholl.json:
--------------------------------------------------------------------------------
1 | {
2 | "font_color": "#3f4266",
3 | "mode_maps":{
4 | "shift":{
5 | "key_pow10":"key_pi",
6 | "key_rcl":"key_STO",
7 | "key_sqrt":"key_sqrt3",
8 | "key_pow2":"key_pow3",
9 | "key_pown":"key_sqrtn",
10 | "key_pow-1":"key_faculty",
11 | "key_ln":"key_epow",
12 | "key_sin":"key_sin-1",
13 | "key_cos":"key_cos-1",
14 | "key_tan":"key_tan-1",
15 | "key_M+":"key_M-",
16 | "key_7":"key_CONST",
17 | "key_integ":"key_deriv",
18 | "key_mode":"key_setup"
19 | },"alpha":{
20 | "key_pow10":"key_e",
21 | "key_(-)":"key_uservar_A",
22 | "key_°":"key_uservar_B",
23 | "key_hyp":"key_uservar_C",
24 | "key_sin":"key_uservar_D",
25 | "key_cos":"key_uservar_E",
26 | "key_tan":"key_uservar_F",
27 | "key_)":"key_uservar_X",
28 | "key_SD":"key_uservar_Y",
29 | "key_M+":"key_uservar_M"
30 | },"STO":{
31 | "key_(-)":"key_STO_A",
32 | "key_°":"key_STO_B",
33 | "key_hyp":"key_STO_C",
34 | "key_sin":"key_STO_D",
35 | "key_cos":"key_STO_E",
36 | "key_tan":"key_STO_F",
37 | "key_)":"key_STO_X",
38 | "key_SD":"key_STO_Y",
39 | "key_M+":"key_STO_M"
40 | },"cmplx":{
41 | "key_eng": "key_i"
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/full_description.txt:
--------------------------------------------------------------------------------
1 | This is a very early version in development.
2 | Unlabeled buttons will be implemented in the future, and bugs may be frequent. If you experience issues or have feature requests, please file an issue (link below).
3 |
4 | Frustrated by the lack of free, open-source scientific calculators that feel like the ones I am used to, I decided to reverse-engineer my old calculator from school.
5 |
6 | With features like:
7 |
8 | - input of complex equations,
9 |
10 | - output as fractions as well as decimals,
11 |
12 | - result storage and reuse,
13 |
14 | - selection and editing of equations from input history,
15 |
16 | - and support for implicit multiplication (like 5π instead of 5*π),
17 |
18 | this calculator produces math that looks like the math on your paper. That makes it much more suitable for higher-level math in school and university—especially if you've grown accustomed to this kind of interface from a young age and just forgot your calculator at home.
19 |
20 | Some additional features include:
21 |
22 | - A well-established open-source math.js library as the math engine—so you’re not relying on some sketchy, hand-rolled math logic (just my sketchy hand-rolled input method 😛)
23 |
24 | - A familiar, intuitive design
25 |
26 | - 100% offline usability and fully open source
27 |
28 | - Written entirely in HTML, JS, and CSS, which means:
29 |
30 | + inherently cross-platform and also available as a website and Flatpak
31 |
32 | + easy for other web developers to contribute
33 |
34 | + admittedly a bit heavy for a calculator. If you're interested in helping make a lighter version (Flutter, Kotlin, etc.), you're very welcome!
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/full_description.txt:
--------------------------------------------------------------------------------
1 | Dies ist eine sehr frühe Version in der Entwicklung. Unbeschriftete Tasten werden in naher Zukunft implementiert, und Bugs können häufig auftreten. Wenn du auf Probleme stößt oder Feature-Wünsche hast, erstelle bitte ein Issue (Link unten).
2 |
3 | Frustriert über den Mangel an freien, Open-Source wissenschaftlichen Taschenrechnern, die sich vertraut anfühlen, habe ich beschlossen, meinen alten Taschenrechner nachzubasteln.
4 |
5 | Mit Funktionen wie:
6 |
7 | - Eingabe komplexer Gleichungen,
8 |
9 | - Ausgabe als Brüche sowie Dezimalzahlen,
10 |
11 | - Speichern und Wiederverwenden von Ergebnissen,
12 |
13 | - Auswahl und Bearbeitung von Gleichungen aus dem Eingabeverlauf,
14 |
15 | - und Unterstützung für implizite Multiplikation (wie 5π statt 5*π),
16 |
17 | erzeugt dieser Rechner Gleichungen, die aussehen wie die auf deinem Papier. Das macht ihn deutlich besser geeignet für höhere Mathematik in Schule und Uni – besonders wenn du von klein auf an diese Art der Bedienung gewöhnt bist und gerade deinen Taschenrechner vergessen hast.
18 |
19 | Einige zusätzliche Features:
20 |
21 | - Eine etablierte Open-Source-Bibliothek (math.js) berechnet die Ergebnisse. Du verlässt dich also nicht auf irgendeine unzuverlässige Eigenentwicklung (nur auf meine Marke-Eigenbau Eingabemethode 😛)
22 |
23 | - Ein vertrautes, intuitives Design
24 |
25 | - 100 % offline nutzbar und vollständig Open-Source
26 |
27 | - Komplett in HTML, JS und CSS geschrieben, was bedeutet:
28 |
29 | + plattformübergreifend im Web und als Flatpak
30 |
31 | + einfacher Einstieg als Webentwickler*innen, falls ihr mitarbeiten wollt.
32 |
33 | + zugegeben etwas schwergewichtig für einen Taschenrechner. Wenn du Interesse hast, eine leichtere Version (Flutter, Kotlin etc.) mitzuentwickeln, bist du herzlich willkommen!
34 |
--------------------------------------------------------------------------------
/update_version.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const glob = require('glob');
4 | const packageJson = require('./package.json');
5 |
6 | // Read config.xml
7 | const configFilePath = path.join(__dirname, 'config.xml');
8 | const versionTxtFilePath = path.join(__dirname, 'www', 'version.txt');
9 | const versionCodeTxtFilePath = path.join(__dirname, 'www', 'versionCode.txt');
10 | let configXml = fs.readFileSync(configFilePath, 'utf8');
11 |
12 | // Find and replace version in config.xml
13 | const versionRegex = /]+version="([^"]+)"/;
14 | const versionMatch = versionRegex.exec(configXml);
15 |
16 | if (versionMatch) {
17 | configXml = configXml.replace(versionMatch[1], packageJson.version);
18 | // Write the updated config.xml file back
19 | fs.writeFileSync(configFilePath, configXml, 'utf8');
20 | } else {
21 | console.error('Could not find version tag in config.xml');
22 | }
23 |
24 | fs.writeFileSync(versionTxtFilePath, packageJson.version.toString(), 'utf8');
25 | fs.writeFileSync(versionCodeTxtFilePath, packageJson.versionCode.toString(), 'utf8');
26 |
27 | // Get all changelog .txt files in fastlane structure
28 | const changelog_files = glob.sync('fastlane/metadata/android/*/changelog/' + packageJson.versionCode + '.txt');
29 | const destDir = path.join('www', 'changelog');
30 |
31 | // Ensure destination directory exists
32 | fs.mkdirSync(destDir, { recursive: true });
33 |
34 | changelog_files.forEach(file => {
35 | // Extract language and file name
36 | const parts = file.split(path.sep);
37 | const lang = parts[3]; // e.g., 'en-US'
38 | const filename = lang + ".txt"
39 |
40 | const destPath = path.join(destDir, filename);
41 |
42 | // Copy file
43 | fs.copyFileSync(file, destPath);
44 |
45 | console.log(`Copied ${file} → ${destPath}`);
46 | });
47 |
48 | // Get all changelog .txt files in fastlane structure
49 | const design_files = glob.sync('www/img/gui/*.svg');
50 | const design_list_destDir = path.join('www', 'img', 'gui', 'list.json');
51 | const design_list = []
52 |
53 | design_files.forEach(file => {
54 | design_list.push(file.split(path.sep)[3].split(".")[0])
55 | });
56 |
57 | fs.writeFileSync(design_list_destDir, JSON.stringify(design_list), 'utf8');
--------------------------------------------------------------------------------
/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Schulrechner
4 | The calculator you know from school
5 |
6 | Joris Yidong Scholl
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/tests/manual.js:
--------------------------------------------------------------------------------
1 | /*
2 | Total Workflow of using the Schulrechner:
3 |
4 | UI loads -> pointer_events assigned to keys ->
5 | pointerdown event on key -> key input_code sent to handle function of input_handler -> input_handler builds math.js string ->
6 | math.js evaluates string -> input_handler creates rendered_input and rendered_output -> set as innerHTML of display elements ->
7 | CSS styles input and results
8 |
9 | What does this test test?
10 | This only tests UI functionality by just iterating over the manual checklist at manual_test_results.csv
11 | */
12 |
13 | const fs = require('fs');
14 | const path = require('path');
15 | const { test, describe } = require('node:test');
16 | const assert = require('node:assert');
17 |
18 | // Get versionCode
19 | const packageJsonPath = path.resolve('package.json');
20 | const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
21 | const versionCode = String(packageJson.versionCode);
22 |
23 | // Load CSV
24 | const csvPath = path.resolve('tests','manual_test_results.csv');
25 | const csvContent = fs.readFileSync(csvPath, 'utf-8').trim();
26 |
27 | // Parse CSV
28 | const [headerLine, ...dataLines] = csvContent.split('\n');
29 | const headers = headerLine.split(',').map(h => h.trim());
30 | const versionIndex = headers.indexOf('versionCode');
31 |
32 | // Find the row for the current versionCode
33 | const row = dataLines.find(line => {
34 | const cols = line.split(',');
35 | return cols[versionIndex] === versionCode;
36 | });
37 |
38 | describe(`🧪 Manual UI Tests for version ${versionCode}`, () => {
39 | if (!row) {
40 | for (let i = 0; i < headers.length; i++) {
41 | const testName = headers[i];
42 | test.skip(`${testName} (no result or unknown value)`, () => { });
43 | }
44 | } else {
45 | const values = row.split(',');
46 |
47 | for (let i = 0; i < headers.length; i++) {
48 | if (i === versionIndex) continue; // skip versionCode column
49 |
50 | const testName = headers[i];
51 | const result = values[i]?.trim().toLowerCase();
52 |
53 | switch (result) {
54 | case 'y':
55 | test(`${testName}`, () => {
56 | assert.ok(true, 'Manually verified');
57 | });
58 | break;
59 | case 'n':
60 | test(`${testName}`, () => {
61 | assert.fail(`❌ Manual test "${testName}" failed for version ${versionCode}`);
62 | });
63 | break;
64 | case 'todo':
65 | test.todo(`${testName} (todo)`);
66 | break;
67 | default:
68 | test.skip(`${testName} (no result or unknown value)`, () => { });
69 | }
70 | }
71 | }
72 | });
73 |
--------------------------------------------------------------------------------
/res/icon/android/background.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
19 |
21 |
27 |
33 |
39 |
45 |
46 |
65 |
70 |
75 |
80 |
85 |
86 |
90 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/www/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Schulrechner
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
⚙️
25 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/CardiJey/schulrechner/actions/workflows/fastlane.yml)
2 | [](https://github.com/CardiJey/schulrechner/actions/workflows/npm_tests.yml)
3 |
4 |
25 |
26 | # SCHULRECHNER
27 |
28 | This is the repository for the Schulrechner App.
29 | You can get the APK directly from the [releases](https://github.com/CardiJey/schulrechner/releases/latest/) or from [F-Droid](https://f-droid.org/packages/io.cardijey.schulrechner). You can also install the Linux-desktop version from [Flathub](https://flathub.org/apps/io.github.CardiJey.schulrechner) or just open the [web version](https://cardijey.github.io/schulrechner).
30 | Alternatively you can build it yourself by installing [Cordova](https://cordova.apache.org/) and then running the steps in the [Github CI](https://github.com/CardiJey/schulrechner/blob/main/.github/workflows/main.yml).
31 |
32 | # Contributing
33 |
34 | If you want to contribute, just fork this repo and create a merge request. Please also create a quick issue with the label "Feature Request" where you describe what you plan to contribute. Maybe someone is already working on that.
35 |
36 | For contributing new calculator designs please read the following:
37 |
38 | ### Adding New GUI Designs
39 |
40 | This project supports multiple GUI designs, which can be switched dynamically.
41 |
42 | To add a new GUI design:
43 |
44 | 1. **Create your SVG file**
45 | - Design your GUI as an SVG file, ensuring it meets the following requirements:
46 | - Use proper element labels (via the `inkscape:label` property) matching the keys found in `www/img/gui/Classic_by_Joris Yidong Scholl.svg`.
47 | - Include polygon elements named `display_input` and `display_output`.
48 | - Include elements `scroll_x_order` and `scroll_y_border` to define scrolling behavior.
49 | - For each key element (`key_*`), include a corresponding `label_background_*` element for button press animations (these are handled by CSS).
50 | - Optionally, add locale-specific labels by appending locale codes like `label_,de-DE` or `label,_en-US`. Only `de-DE` and `en-US` are supported for now.
51 | - Add SVG metadata specifying **author**, **date**, and an **open-source license** (e.g., CC0, CC-BY, CC-BY-SA) for compatibility with this GPL3 project.
52 |
53 | 2. **Create your JSON file**
54 | - There are some additional settings needed for a working calculator design
55 | - Please take a look at `www/img/gui/Classic_by_Joris Yidong Scholl.json` to see what these are
56 |
57 | 3. **Create a pull request to add your SVG file to the repository**
58 | - Place your GUI SVG and JSON in the `www/img/gui/` folder in a pull request. They should be named "Design Name\_by\_Author Name.svg" and ".json"
59 |
60 | 4. **I'll come test it then and we'll fix any possible errors together**
61 |
62 | # Copyright
63 |
64 | https://github.com/CardiJey/schulrechner
65 |
66 | Copyright (C) 2025 Joris Yidong Scholl
67 |
68 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
69 |
70 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
71 |
72 | You should have received a copy of the GNU General Public License along with this program. If not, see .
73 |
74 | # Dependencies
75 |
76 | This project uses math.js
77 | https://github.com/josdejong/mathjs
78 |
79 | Copyright (C) 2013-2025 Jos de Jong
80 |
81 | Licensed under the Apache License, Version 2.0 (the "License");
82 | you may not use this file except in compliance with the License.
83 | You may obtain a copy of the License at
84 |
85 | https://www.apache.org/licenses/LICENSE-2.0
86 |
87 | Unless required by applicable law or agreed to in writing, software
88 | distributed under the License is distributed on an "AS IS" BASIS,
89 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
90 | See the License for the specific language governing permissions and
91 | limitations under the License.
92 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Build Cordova APK
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*' # This will trigger the workflow for any version tag like v1.0.0, v2.1.3, etc.
7 | workflow_dispatch:
8 | pull_request:
9 | branches:
10 | - main
11 |
12 | permissions:
13 | contents: write
14 |
15 | jobs:
16 | build:
17 | runs-on: ubuntu-latest
18 |
19 | steps:
20 | - name: Checkout repo
21 | uses: actions/checkout@v6
22 | with:
23 | fetch-depth: 0 # Ensures full history is fetched
24 | fetch-tags: true
25 |
26 | - name: Set up Node.js
27 | uses: actions/setup-node@v6
28 | with:
29 | node-version: 24
30 |
31 | - name: Generate Sources
32 | run: |
33 | pipx install --force git+https://github.com/flatpak/flatpak-builder-tools.git#subdirectory=node
34 | flatpak-node-generator npm package-lock.json
35 |
36 | - name: Upload generated-sources.json
37 | uses: actions/upload-artifact@v6
38 | with:
39 | name: generated-sources
40 | path: generated-sources.json
41 |
42 | - name: Install dependencies
43 | run: |
44 | npm install
45 |
46 | - name: Build and move math.js bundle
47 | run: |
48 | git submodule update --init --recursive
49 | cd mathjs
50 | npm install
51 | npm run build
52 | cd ..
53 | mv mathjs/lib/browser/math.js www/js/math.js
54 |
55 | - name: Update XML Version and Copy Fastlane Changelog
56 | run: npm run update_version
57 |
58 | - name: Extract Changelog
59 | run: git log --pretty=format:"- %s" > www/changelog/commit_messages.txt
60 |
61 | # === Android Cordova Build ===
62 |
63 | - name: Set up Java
64 | uses: actions/setup-java@v5
65 | with:
66 | distribution: 'temurin'
67 | java-version: '17'
68 |
69 | - name: Set up Android SDK
70 | uses: android-actions/setup-android@v3
71 |
72 | - name: Install Cordova
73 | run: npm install -g cordova
74 |
75 | - name: Prepare Android Gradle project
76 | run: |
77 | cordova platform add android
78 |
79 | - name: Build Cordova project
80 | run: |
81 | cordova build android --release "--" --packageType=apk
82 |
83 | - name: Decode Keystore from secret
84 | run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > schulrechner.jks
85 |
86 | - name: Find latest Build Tools version
87 | id: buildtools
88 | run: |
89 | LATEST=$(ls -v "$ANDROID_HOME/build-tools" | tail -n 1)
90 | echo "version=$LATEST" >> $GITHUB_OUTPUT
91 |
92 | - name: Sign the APK with apksigner
93 | run: |
94 | BUILD_TOOLS_VERSION=${{ steps.buildtools.outputs.version }}
95 |
96 | $ANDROID_HOME/build-tools/$BUILD_TOOLS_VERSION/zipalign -v 4 \
97 | platforms/android/app/build/outputs/apk/release/app-release-unsigned.apk \
98 | platforms/android/app/build/outputs/apk/release/app-release-aligned.apk
99 |
100 | $ANDROID_HOME/build-tools/$BUILD_TOOLS_VERSION/apksigner sign \
101 | --ks schulrechner.jks \
102 | --ks-key-alias ${{ secrets.KEY_ALIAS }} \
103 | --ks-pass pass:${{ secrets.KEYSTORE_PASSWORD }} \
104 | --key-pass pass:${{ secrets.KEY_PASSWORD }} \
105 | --out platforms/android/app/build/outputs/apk/release/app-release.apk \
106 | platforms/android/app/build/outputs/apk/release/app-release-aligned.apk
107 |
108 | - name: Upload APK artifact
109 | uses: actions/upload-artifact@v6
110 | with:
111 | name: schulrechner-apk
112 | path: platforms/android/app/build/outputs/apk/release/app-release.apk
113 |
114 | - name: Upload Release Assets
115 | if: startsWith(github.ref_name, 'v')
116 | uses: softprops/action-gh-release@v2
117 | with:
118 | files: |
119 | dist/*.flatpak
120 | platforms/android/app/build/outputs/apk/release/app-release.apk
121 | generated-sources.json
122 | env:
123 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
124 |
125 | - name: Prepare Browser Gradle project
126 | if: startsWith(github.ref_name, 'v')
127 | run: |
128 | cordova platform add browser
129 |
130 | - name: Build Cordova Browser project
131 | if: startsWith(github.ref_name, 'v')
132 | run: |
133 | cordova build browser --release
134 |
135 | - name: Push Browser Files to browser-release branch
136 | if: startsWith(github.ref_name, 'v')
137 | run: |
138 | BROWSER_BRANCH="browser-release"
139 | TAG_NAME="${GITHUB_REF##*/}"
140 |
141 | # Clone with access token for pushing
142 | git clone --depth=1 --single-branch --branch $BROWSER_BRANCH https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }} temp-browser
143 | cd temp-browser
144 |
145 | git config user.name "github-actions[bot]"
146 | git config user.email "github-actions[bot]@users.noreply.github.com"
147 |
148 | # Remove all contents from brwoser branch
149 | git rm -rf . --ignore-unmatch
150 |
151 | # Copy unbuilt Android project
152 | cp -r ../platforms/browser/* .
153 |
154 | # Copy browser redirection file
155 | cp ../other/browser_redirection.html index.html
156 |
157 | git add .
158 | git commit -m "Browser release of ${TAG_NAME}" || echo "No changes to commit"
159 |
160 | # Push to branch and tag
161 | git push origin $GRADLE_BRANCH
162 |
--------------------------------------------------------------------------------
/flatpak/io.github.CardiJey.schulrechner.metainfo.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | io.github.CardiJey.schulrechner
4 |
5 | Schulrechner
6 | The calculator you know from school
7 |
8 | https://cardijey.github.io#schulrechner
9 |
10 | MIT
11 | GPL-3.0-only
12 |
13 |
14 | pointing
15 | keyboard
16 | touch
17 |
18 |
19 |
20 | This is a very early version in development. Unlabeled buttons will be implemented in the future, and bugs may be frequent. If you experience issues or have feature requests, please file an issue (link below).
21 | Frustrated by the lack of free, open-source scientific calculators that feel like the ones I am used to, I decided to reverse-engineer my old calculator from school.
22 |
23 | With features like:
24 |
25 | input of complex equations,
26 | output as fractions as well as decimals,
27 | result storage and reuse,
28 | selection and editing of equations from input history,
29 | and support for implicit multiplication (like 5π instead of 5*π),
30 | this calculator produces math that looks like the math on your paper. That makes it much more suitable for higher-level math in school and university—especially if you've grown accustomed to this kind of interface from a young age and just forgot your calculator at home.
31 |
32 | Some additional features include:
33 |
34 | A well-established open-source math.js library as the math engine—so you're not relying on some sketchy, hand-rolled math logic (just my sketchy hand-rolled input method 😛)
35 | A familiar, intuitive design
36 | 100% offline usability and fully open source
37 | Written entirely in HTML, JS, and CSS, which means it is inherently cross-platform, ...
38 | easy for other web developers to contribute and ...
39 | admittedly a bit heavy for a calculator. If you're interested in helping make a lighter version (Flutter, Kotlin, etc.), you're very welcome!
40 |
41 |
42 |
43 |
44 | #7b8c86
45 | #40424d
46 |
47 |
48 | io.github.CardiJey.schulrechner.desktop
49 |
50 |
51 | https://cardijey.github.io/assets/img/schulrechner/featureGraphic.png
52 | Three scientific calculators on a black background, each showing a mint-green display and rows of buttons in shades of gray with orange accents
53 |
54 |
55 |
56 | Joris Yidong Scholl
57 |
58 |
59 |
60 |
61 |
62 |
63 | Feature: °′‴-button now switches to DMS format
64 | Feature: Added option to toggle between Deg, Rad and Gra angle modes
65 | Tweak: Updated dependencies
66 |
67 |
68 |
69 |
70 |
71 |
72 | Feature: Added config option to prefer decimal results
73 | Feature: Added mode menu
74 | Feature: Added CMPLX mode and i
75 | Feature: Added integration
76 | Feature: Added differentiation
77 | Feature: Added setup menu
78 | Feature: Added Norm and Fix rounding setup modes
79 | Feature: Added sexagesimal (hours, minutes, seconds) button
80 | Bugfix: Eliminated rounding errors in trigonometric functions
81 | Bugfix: Cube root of -1 now computes to -1
82 | Tweak: Updated dependencies
83 |
84 |
85 |
86 |
87 |
88 |
89 | Feature: Added (-) button
90 | Feature: Added keyboard support for desktop version
91 | Feature: Added hyperbolic functions
92 | Tweak: "Renovate" integrated to ensure up-to-date dependencies
93 | Tweak: Updated dependencies
94 |
95 |
96 |
97 |
98 |
99 |
100 | Feature: Added option to switch between multiple designs (although currently only the default is available, more designs are coming!)
101 | Feature: Added constants (a way to view the list of constants is planned)
102 | Bugfix: Very large results no longer cause the app to hang
103 | Bugfix: Font scaling fixed, preventing cutoff and incorrect line wrapping
104 | Bugfix: Sums in trigonometric functions are now input correctly
105 |
106 |
107 |
108 |
109 |
110 |
111 | Schulrechner now also available on Flathub!
112 | Bugfix: Output should no longer cut off
113 | Feature: Added settings menu with info links
114 | Feature: Added option to switch decimal format for other locales
115 | Feature: Added ASin, ACos and ATan
116 | Feature: Added M+ and M-
117 |
118 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/tests/svg.js:
--------------------------------------------------------------------------------
1 | /*
2 | What does this test test?
3 | This tests that all SVG in the www/img/gui are valid and contain the necessary metadata
4 | */
5 |
6 | const fs = require('fs');
7 | const path = require('path');
8 | const { test, describe } = require('node:test');
9 | const assert = require('node:assert');
10 | const xml2js = require('xml2js');
11 |
12 | const directory = path.join(__dirname, '..', 'www', 'img', 'gui');
13 |
14 | describe('SVG Metadata Tests', () => {
15 | const files = fs.readdirSync(directory).filter(file => file.endsWith('.svg'));
16 |
17 | files.forEach(file => {
18 | test(`SVG ${file} filename should match _by_.svg`, (t) => {
19 | const pattern = /^.+_by_.+\.svg$/;
20 |
21 | assert.ok(
22 | pattern.test(file),
23 | `${file} does not match the expected pattern "_by_.svg"`
24 | );
25 | });
26 | test(`There should be a corresponding JSON file for ${file}`, (t) => {
27 | const json_file_name = file.substring(0,file.length - 4) + ".json"
28 | const json_file_path = path.join(directory, json_file_name);
29 | const json_file = fs.readFileSync(json_file_path, 'utf8');
30 | const json_object = JSON.parse(json_file)
31 |
32 | const hexColorPattern = /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/;
33 |
34 | assert.ok(
35 | hexColorPattern.test(json_object.font_color),
36 | `${json_file_name} font_color is not a valid hex color`
37 | );
38 | assert.deepStrictEqual(typeof json_object.mode_maps.shift,"object")
39 | assert.deepStrictEqual(typeof json_object.mode_maps.cmplx,"object")
40 | assert.deepStrictEqual(typeof json_object.mode_maps.alpha,"object")
41 | assert.deepStrictEqual(typeof json_object.mode_maps.STO,"object")
42 | })
43 | test(`SVG ${file} should contain an author`, (t) => {
44 | const filePath = path.join(directory, file);
45 | const content = fs.readFileSync(filePath, 'utf8');
46 | xml2js.parseString(content, (err, result) => {
47 | if (err) {
48 | assert.fail(`XML parse error in ${file}: ${err}`)
49 | }
50 |
51 | try {
52 | // Navigate metadata -> rdf:RDF -> cc:Work -> dc:creator -> cc:Agent -> dc:title
53 | const metadata = result.svg.metadata?.[0];
54 | if (!metadata) throw new Error('No found');
55 |
56 | const rdf = metadata['rdf:RDF']?.[0];
57 | if (!rdf) throw new Error('No found');
58 |
59 | const work = rdf['cc:Work']?.[0];
60 | if (!work) throw new Error('No found');
61 |
62 | const creator = work['dc:creator']?.[0];
63 | if (!creator) throw new Error('No found');
64 |
65 | const agent = creator['cc:Agent']?.[0];
66 | if (!agent) throw new Error('No found');
67 |
68 | const title = agent['dc:title']?.[0];
69 | if (!title) throw new Error('No found');
70 |
71 | console.log(`Author for ${file}: "${title.trim()}"`);
72 | assert.notStrictEqual(title.trim(),"")
73 | } catch (e) {
74 | assert.fail(`${file}: ${e.message}`)
75 | }
76 | });
77 | });
78 | test(`SVG ${file} should contain an creation date`, (t) => {
79 | const filePath = path.join(directory, file);
80 | const content = fs.readFileSync(filePath, 'utf8');
81 | xml2js.parseString(content, (err, result) => {
82 | if (err) {
83 | assert.fail(`XML parse error in ${file}: ${err}`)
84 | }
85 |
86 | try {
87 | // Navigate metadata -> rdf:RDF -> cc:Work -> dc:creator -> cc:Agent -> dc:title
88 | const metadata = result.svg.metadata?.[0];
89 | if (!metadata) throw new Error('No found');
90 |
91 | const rdf = metadata['rdf:RDF']?.[0];
92 | if (!rdf) throw new Error('No found');
93 |
94 | const work = rdf['cc:Work']?.[0];
95 | if (!work) throw new Error('No found');
96 |
97 | const svg_date = work['dc:date']?.[0];
98 | if (!svg_date) throw new Error('No found');
99 |
100 | console.log(`Date for ${file}: "${svg_date.trim()}"`);
101 | assert.notStrictEqual(svg_date.trim(),"")
102 | } catch (e) {
103 | assert.fail(`${file}: ${e.message}`)
104 | }
105 | });
106 | });
107 | test(`SVG ${file} should contain an open source license`, (t) => {
108 | const filePath = path.join(directory, file);
109 | const content = fs.readFileSync(filePath, 'utf8');
110 | xml2js.parseString(content, (err, result) => {
111 | if (err) {
112 | assert.fail(`XML parse error in ${file}: ${err}`)
113 | }
114 |
115 | try {
116 | // Navigate metadata -> rdf:RDF -> cc:Work -> dc:creator -> cc:Agent -> dc:title
117 | const metadata = result.svg.metadata?.[0];
118 | if (!metadata) throw new Error('No found');
119 |
120 | const rdf = metadata['rdf:RDF']?.[0];
121 | if (!rdf) throw new Error('No found');
122 |
123 | const work = rdf['cc:Work']?.[0];
124 | if (!work) throw new Error('No found');
125 |
126 | const license = work['cc:license']?.[0];
127 | if (!license) throw new Error('No found');
128 |
129 | const license_url = license['$']['rdf:resource']
130 | console.log(`License for ${file}: "${license_url}"`);
131 | const validExpected = [
132 | "http://creativecommons.org/licenses/by-sa/4.0/",
133 | "http://creativecommons.org/licenses/by/4.0/",
134 | "http://creativecommons.org/publicdomain/zero/1.0/"
135 | ];
136 | assert.ok(validExpected.includes(license_url), `Expected one of ${validExpected.join(', ')}, but got ${license_url}`);
137 | } catch (e) {
138 | assert.fail(`${file}: ${e.message}`)
139 | }
140 | });
141 | });
142 | });
143 | });
144 |
--------------------------------------------------------------------------------
/www/css/styles.css:
--------------------------------------------------------------------------------
1 | :root {
2 | /* Farbe #3f4266 als CSS-Variable */
3 | --main-color: #3f4266;
4 | }
5 |
6 | /* Regular */
7 | @font-face {
8 | font-family: 'Schulrechner';
9 | src: url('../fonts/Schulrechner-Regular.ttf') format('truetype');
10 | font-style: normal;
11 | font-weight: normal;
12 | }
13 |
14 | /* Italic */
15 | @font-face {
16 | font-family: 'Schulrechner';
17 | src: url('../fonts/Schulrechner-Italic.ttf') format('truetype');
18 | font-style: italic;
19 | font-weight: normal;
20 | }
21 |
22 | @keyframes blink {
23 | 0%, 50% {
24 | visibility: visible;
25 | }
26 | 51%, 100% {
27 | visibility: hidden;
28 | }
29 | }
30 |
31 | body {
32 | font-family: 'Schulrechner', monospace;
33 | font-size: 1rem;
34 | color: var(--main-color);
35 | }
36 |
37 | html,body {
38 | text-size-adjust: none !important;
39 | -webkit-text-size-adjust: none !important;
40 | -ms-text-size-adjust: none !important;
41 | -moz-text-size-adjust: none !important;
42 | }
43 |
44 | svg {
45 | width: 100vw;
46 | height: 100vh;
47 | position: absolute;
48 | top: 0;
49 | left: 0;
50 | box-sizing: border-box;
51 | }
52 |
53 | .math-div{
54 | position: absolute;
55 | padding-left: 0;
56 | padding-right: 0;
57 | padding-bottom: 0;
58 | margin: 0;
59 | padding-left: 0.111rem;
60 | overflow: hidden;
61 | scrollbar-width: 0;
62 | white-space: nowrap;
63 | }
64 |
65 | #math-output {
66 | display: flex;
67 | padding-top: 0;
68 | flex-wrap: wrap;
69 | align-content: flex-end;
70 | justify-content: flex-end;
71 | text-align: right;
72 | }
73 |
74 | #math-input {
75 | text-align: left !important;
76 | padding-top: 0.222rem;
77 | }
78 |
79 | [inkscape\3a label^="label_"] {
80 | pointer-events: none;
81 | }
82 |
83 | [inkscape\3a label="display_input"] {
84 | position: absolute;
85 | overflow: hidden;
86 | padding: 0;
87 | margin: 0;
88 | padding-left: 0.111rem;
89 | }
90 |
91 |
92 | [inkscape\3a label^="indicator_"] {
93 | visibility: hidden;
94 | }
95 |
96 | .sqrt {
97 | border-top: 0.111em solid var(--main-color);
98 | padding-right: 0.222em;
99 | padding-top: 0.111em;
100 | display: inline-block;
101 | }
102 |
103 | .scale_height {
104 | transform-origin: center;
105 | }
106 |
107 | .sqrt_wrapper {
108 | display: inline-flex;
109 | align-items: center;
110 | }
111 |
112 | .frac_wrapper {
113 | display: inline-block;
114 | text-align: center;
115 | vertical-align: 0px;
116 | /*height: calc(2 * 0.667rem + 3 * 0.111rem);*/
117 | line-height: 1;
118 | margin-right: 0.111rem;
119 | }
120 |
121 | .frac_top, .frac_bottom {
122 | position: relative;
123 | padding: 0;
124 | margin: 0;
125 | left: 0;
126 | display: block;
127 | padding-left: 0.111rem;
128 | font-size: 0.667rem;
129 | overflow: visible;
130 | justify-content: center;
131 | }
132 |
133 | .frac_top {
134 | align-items: flex-end;
135 | }
136 |
137 | .frac_bottom {
138 | border-top: 0.111rem solid var(--main-color);
139 | padding-top: 0.111rem;
140 | margin-top: 0.111rem;
141 | }
142 |
143 | .integ_wrapper {
144 | display: inline-flex;
145 | align-items: center;
146 | }
147 |
148 | .integ_wrapper_2 {
149 | display: inline-flex;
150 | flex-direction: row-reverse;
151 | align-items: center;
152 | }
153 |
154 | .integ_wrapper_3{
155 | display: inline-flex;
156 | flex-direction: column-reverse;
157 | }
158 |
159 | .integ_top, .integ_bottom{
160 | font-size: 0.667rem;
161 | }
162 |
163 | .cursor {
164 | width: 0;
165 | padding: 0;
166 | margin: 0;
167 | height: 1em;
168 | animation: blink 1s infinite;
169 | }
170 |
171 | .period {
172 | display: inline-block;
173 | border-top: 0.111rem solid var(--main-color);
174 | }
175 |
176 | .pow_top {
177 | display: inline-block;
178 | vertical-align: 0.667em;
179 | font-size: 0.667rem;
180 | }
181 |
182 | .pow_bottom {
183 | display: inline-block;
184 | padding-left: 0.2rem;
185 | }
186 |
187 | .logn_bottom {
188 | display: inline-block;
189 | vertical-align: -0.333em;
190 | font-size: 0.667rem;
191 | }
192 |
193 | .pressed {
194 | filter: brightness(85%);
195 | transition: transform 0.1s, filter 0.1s;
196 | }
197 |
198 | .changelog-div {
199 | position: absolute;
200 | top: 0;
201 | left: 0;
202 | bottom: 0;
203 | right: 0;
204 | font-family: auto;
205 | font-size: 0.8rem;
206 | padding: 3vw;
207 | overflow: hidden;
208 | background-color: rgba(0,0,0,0.8);
209 | color: white;
210 | visibility: hidden;
211 | }
212 |
213 | #version-small {
214 | position: fixed;
215 | bottom: 0;
216 | left: 0;
217 | font-size: 0.6rem;
218 | }
219 |
220 | #changelog {
221 | overflow-y: auto;
222 | }
223 |
224 | #changelog-content span {
225 | display: block;
226 | }
227 |
228 | .changelog-tweak {
229 | color: gray;
230 | }
231 |
232 | .changelog-feature {
233 | color: rgb(0,255,0);
234 | }
235 |
236 | #open-settings-btn {
237 | position: absolute;
238 | bottom: 0.1rem;
239 | right: 0.1rem;
240 | background-color: #1e1e2f;
241 | color: #ffffff;
242 | border: none;
243 | border-radius: 0.25rem;
244 | font-size: 0.7rem;
245 | padding: 0.25rem 0.3rem;
246 | cursor: pointer;
247 | }
248 |
249 | #open-settings-btn:hover {
250 | background-color: #2c2c3e;
251 | transform: scale(1.05);
252 | }
253 |
254 | #settings-menu {
255 | position: absolute;
256 | bottom: 3rem;
257 | right: 1rem;
258 | left: 1rem;
259 |
260 | background-color: #1e1e2f;
261 | color: #ffffff;
262 | padding: 1rem;
263 | border-radius: 0.75rem;
264 | box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
265 |
266 | display: flex;
267 | flex-direction: column;
268 | gap: 0.75rem;
269 |
270 | font-size: 0.9rem;
271 | font-family: auto;
272 | transition: opacity 0.3s ease, transform 0.3s ease;
273 | z-index: 100;
274 | }
275 |
276 | #settings-menu input[type="checkbox"] {
277 | accent-color: #696982;
278 | width: 1rem;
279 | height: 1rem;
280 | cursor: pointer;
281 | }
282 |
283 | #settings-menu.hidden {
284 | opacity: 0;
285 | pointer-events: none;
286 | transform: translateY(10px);
287 | }
288 |
289 | #settings-menu h3 {
290 | margin: 0;
291 | font-size: 1rem;
292 | border-bottom: 1px solid #333;
293 | padding-bottom: 0.5rem;
294 | }
295 |
296 | #settings-menu label {
297 | display: flex;
298 | align-items: center;
299 | gap: 0.5rem;
300 | cursor: pointer;
301 | }
302 |
303 | #close-settings-btn {
304 | align-self: flex-end;
305 | background-color: #2c2c3e;
306 | color: #fff;
307 | border: none;
308 | padding: 0.3rem 0.6rem;
309 | border-radius: 0.5rem;
310 | cursor: pointer;
311 | font-size: 0.8rem;
312 | transition: background-color 0.2s ease;
313 | }
314 |
315 | #close-settings-btn:hover {
316 | background-color: #3d3d52;
317 | }
318 |
319 | #decimal-format-select {
320 | background-color: #2c2c3e;
321 | color: #fff;
322 | border: 1px solid #444;
323 | border-radius: 0.5rem;
324 | padding: 0.4rem;
325 | font-size: 0.9rem;
326 | width: 100%;
327 | margin-top: 0.25rem;
328 | }
329 |
330 | #decimal-format-select:focus {
331 | outline: none;
332 | border-color: #666;
333 | box-shadow: 0 0 0 2px rgba(100, 100, 255, 0.4);
334 | }
335 |
336 | #settings-menu a {
337 | color: inherit;
338 | }
339 |
340 | .design-select{
341 | display: flex;
342 | overflow: auto;
343 | }
344 |
345 | .design-option img{
346 | height: 3rem;
347 | }
348 |
349 | .design-option{
350 | display: flex;
351 | flex-direction: column;
352 | align-content: center;
353 | justify-content: space-between;
354 | min-width: 3rem;
355 | max-width: 3rem;
356 | width: 3rem;
357 | flex-wrap: wrap;
358 | background-color: #2c2c3e;
359 | color: #fff;
360 | border: 1px solid #444;
361 | border-radius: 0.5rem;
362 | padding: 0.1rem;
363 | font-size: 0.9rem;
364 | }
365 |
366 | .design-selected{
367 | background-color: #696982;
368 | }
369 |
370 | .design-name-label{
371 | text-align: center;
372 | }
373 |
374 | .design-author-label {
375 | text-align: center;
376 | font-size: 0.5rem;
377 | }
378 |
--------------------------------------------------------------------------------
/res/icon/android/monochrome.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
14 |
15 |
--------------------------------------------------------------------------------
/res/icon/android/monochrome.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
14 |
20 |
26 |
32 |
38 |
39 |
43 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/res/icon/android/splash.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
14 |
16 |
18 |
24 |
27 |
30 |
31 |
32 |
33 |
36 |
39 |
42 |
45 |
48 |
51 |
54 |
57 |
60 |
63 |
66 |
69 |
72 |
75 |
78 |
81 |
84 |
87 |
90 |
93 |
96 |
99 |
102 |
105 |
108 |
111 |
114 |
117 |
120 |
123 |
126 |
129 |
132 |
135 |
138 |
141 |
144 |
147 |
150 |
153 |
156 |
159 |
162 |
165 |
168 |
171 |
174 |
177 |
180 |
184 |
188 |
192 |
196 |
197 |
--------------------------------------------------------------------------------
/res/icon/android/foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
14 |
16 |
18 |
24 |
27 |
30 |
31 |
32 |
33 |
36 |
39 |
42 |
45 |
48 |
51 |
54 |
57 |
60 |
63 |
66 |
69 |
72 |
75 |
78 |
81 |
84 |
87 |
90 |
93 |
96 |
99 |
102 |
105 |
108 |
111 |
114 |
117 |
120 |
123 |
126 |
129 |
132 |
135 |
138 |
141 |
144 |
147 |
150 |
153 |
156 |
159 |
162 |
165 |
168 |
171 |
174 |
177 |
180 |
184 |
188 |
192 |
196 |
197 |
--------------------------------------------------------------------------------
/www/fonts/Schulrechner-Italic.sfd:
--------------------------------------------------------------------------------
1 | SplineFontDB: 3.2
2 | FontName: Schulrechner-Italic
3 | FullName: Schulrechner Italic
4 | FamilyName: Schulrechner
5 | Weight: Italic
6 | Copyright: Copyright (c) 2025, Joris SchollS
7 | UComments: "2025-4-12: Created with FontForge (http://fontforge.org)"
8 | Version: 001.000
9 | ItalicAngle: 0
10 | UnderlinePosition: -10
11 | UnderlineWidth: 5
12 | Ascent: 108
13 | Descent: 0
14 | InvalidEm: 0
15 | LayerCount: 2
16 | Layer: 0 0 "Back" 1
17 | Layer: 1 0 "Fore" 0
18 | XUID: [1021 121 -1838891423 8674590]
19 | StyleMap: 0x0000
20 | FSType: 0
21 | OS2Version: 0
22 | OS2_WeightWidthSlopeOnly: 0
23 | OS2_UseTypoMetrics: 1
24 | CreationTime: 1744471654
25 | ModificationTime: 1757226745
26 | OS2TypoAscent: 0
27 | OS2TypoAOffset: 1
28 | OS2TypoDescent: 0
29 | OS2TypoDOffset: 1
30 | OS2TypoLinegap: 10
31 | OS2WinAscent: 0
32 | OS2WinAOffset: 1
33 | OS2WinDescent: 0
34 | OS2WinDOffset: 1
35 | HheadAscent: 0
36 | HheadAOffset: 1
37 | HheadDescent: 0
38 | HheadDOffset: 1
39 | OS2Vendor: 'PfEd'
40 | MarkAttachClasses: 1
41 | DEI: 91125
42 | LangName: 1033 "" "" "" "" "" "" "" "" "" "" "" "" "" "This Font Software is licensed under the SIL Open Font License, Version 1.1.+AAoA-This license is copied below, and is also available with a FAQ at:+AAoA-http://scripts.sil.org/OFL+AAoACgAK------------------------------------------------------------+AAoA-SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007+AAoA------------------------------------------------------------+AAoACgAA-PREAMBLE+AAoA-The goals of the Open Font License (OFL) are to stimulate worldwide+AAoA-development of collaborative font projects, to support the font creation+AAoA-efforts of academic and linguistic communities, and to provide a free and+AAoA-open framework in which fonts may be shared and improved in partnership+AAoA-with others.+AAoACgAA-The OFL allows the licensed fonts to be used, studied, modified and+AAoA-redistributed freely as long as they are not sold by themselves. The+AAoA-fonts, including any derivative works, can be bundled, embedded, +AAoA-redistributed and/or sold with any software provided that any reserved+AAoA-names are not used by derivative works. The fonts and derivatives,+AAoA-however, cannot be released under any other type of license. The+AAoA-requirement for fonts to remain under this license does not apply+AAoA-to any document created using the fonts or their derivatives.+AAoACgAA-DEFINITIONS+AAoAIgAA-Font Software+ACIA refers to the set of files released by the Copyright+AAoA-Holder(s) under this license and clearly marked as such. This may+AAoA-include source files, build scripts and documentation.+AAoACgAi-Reserved Font Name+ACIA refers to any names specified as such after the+AAoA-copyright statement(s).+AAoACgAi-Original Version+ACIA refers to the collection of Font Software components as+AAoA-distributed by the Copyright Holder(s).+AAoACgAi-Modified Version+ACIA refers to any derivative made by adding to, deleting,+AAoA-or substituting -- in part or in whole -- any of the components of the+AAoA-Original Version, by changing formats or by porting the Font Software to a+AAoA-new environment.+AAoACgAi-Author+ACIA refers to any designer, engineer, programmer, technical+AAoA-writer or other person who contributed to the Font Software.+AAoACgAA-PERMISSION & CONDITIONS+AAoA-Permission is hereby granted, free of charge, to any person obtaining+AAoA-a copy of the Font Software, to use, study, copy, merge, embed, modify,+AAoA-redistribute, and sell modified and unmodified copies of the Font+AAoA-Software, subject to the following conditions:+AAoACgAA-1) Neither the Font Software nor any of its individual components,+AAoA-in Original or Modified Versions, may be sold by itself.+AAoACgAA-2) Original or Modified Versions of the Font Software may be bundled,+AAoA-redistributed and/or sold with any software, provided that each copy+AAoA-contains the above copyright notice and this license. These can be+AAoA-included either as stand-alone text files, human-readable headers or+AAoA-in the appropriate machine-readable metadata fields within text or+AAoA-binary files as long as those fields can be easily viewed by the user.+AAoACgAA-3) No Modified Version of the Font Software may use the Reserved Font+AAoA-Name(s) unless explicit written permission is granted by the corresponding+AAoA-Copyright Holder. This restriction only applies to the primary font name as+AAoA-presented to the users.+AAoACgAA-4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font+AAoA-Software shall not be used to promote, endorse or advertise any+AAoA-Modified Version, except to acknowledge the contribution(s) of the+AAoA-Copyright Holder(s) and the Author(s) or with their explicit written+AAoA-permission.+AAoACgAA-5) The Font Software, modified or unmodified, in part or in whole,+AAoA-must be distributed entirely under this license, and must not be+AAoA-distributed under any other license. The requirement for fonts to+AAoA-remain under this license does not apply to any document created+AAoA-using the Font Software.+AAoACgAA-TERMINATION+AAoA-This license becomes null and void if any of the above conditions are+AAoA-not met.+AAoACgAA-DISCLAIMER+AAoA-THE FONT SOFTWARE IS PROVIDED +ACIA-AS IS+ACIA, WITHOUT WARRANTY OF ANY KIND,+AAoA-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF+AAoA-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT+AAoA-OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE+AAoA-COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,+AAoA-INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL+AAoA-DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING+AAoA-FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM+AAoA-OTHER DEALINGS IN THE FONT SOFTWARE." "http://scripts.sil.org/OFL"
43 | Encoding: UnicodeBmp
44 | UnicodeInterp: none
45 | NameList: AGL For New Fonts
46 | DisplaySize: -48
47 | AntiAlias: 1
48 | FitToEm: 0
49 | WinInfo: 8702 19 15
50 | BeginPrivate: 0
51 | EndPrivate
52 | BeginChars: 65536 9
53 |
54 | StartChar: uni0000
55 | Encoding: 0 0 0
56 | Width: 72
57 | Flags: HW
58 | LayerCount: 2
59 | Fore
60 | SplineSet
61 | 48 12 m 1
62 | 59 12 l 1
63 | 59 1 l 1
64 | 48 1 l 1
65 | 48 12 l 1
66 | 48 24 m 1
67 | 59 24 l 1
68 | 59 13 l 1
69 | 48 13 l 1
70 | 48 24 l 1
71 | 48 36 m 1
72 | 59 36 l 1
73 | 59 25 l 1
74 | 48 25 l 1
75 | 48 36 l 1
76 | 48 48 m 1
77 | 59 48 l 1
78 | 59 37 l 1
79 | 48 37 l 1
80 | 48 48 l 1
81 | 48 60 m 1
82 | 59 60 l 1
83 | 59 49 l 1
84 | 48 49 l 1
85 | 48 60 l 1
86 | 48 72 m 1
87 | 59 72 l 1
88 | 59 61 l 1
89 | 48 61 l 1
90 | 48 72 l 1
91 | 48 84 m 1
92 | 59 84 l 1
93 | 59 73 l 1
94 | 48 73 l 1
95 | 48 84 l 1
96 | 48 96 m 1
97 | 59 96 l 1
98 | 59 85 l 1
99 | 48 85 l 1
100 | 48 96 l 1
101 | 48 108 m 1
102 | 59 108 l 1
103 | 59 97 l 1
104 | 48 97 l 1
105 | 48 108 l 1
106 | 36 12 m 1
107 | 47 12 l 1
108 | 47 1 l 1
109 | 36 1 l 1
110 | 36 12 l 1
111 | 36 24 m 1
112 | 47 24 l 1
113 | 47 13 l 1
114 | 36 13 l 1
115 | 36 24 l 1
116 | 36 36 m 1
117 | 47 36 l 1
118 | 47 25 l 1
119 | 36 25 l 1
120 | 36 36 l 1
121 | 36 48 m 1
122 | 47 48 l 1
123 | 47 37 l 1
124 | 36 37 l 1
125 | 36 48 l 1
126 | 36 60 m 1
127 | 47 60 l 1
128 | 47 49 l 1
129 | 36 49 l 1
130 | 36 60 l 1
131 | 36 72 m 1
132 | 47 72 l 1
133 | 47 61 l 1
134 | 36 61 l 1
135 | 36 72 l 1
136 | 36 84 m 1
137 | 47 84 l 1
138 | 47 73 l 1
139 | 36 73 l 1
140 | 36 84 l 1
141 | 36 96 m 1
142 | 47 96 l 1
143 | 47 85 l 1
144 | 36 85 l 1
145 | 36 96 l 1
146 | 36 108 m 1
147 | 47 108 l 1
148 | 47 97 l 1
149 | 36 97 l 1
150 | 36 108 l 1
151 | 24 12 m 1
152 | 35 12 l 1
153 | 35 1 l 1
154 | 24 1 l 1
155 | 24 12 l 1
156 | 24 24 m 1
157 | 35 24 l 1
158 | 35 13 l 1
159 | 24 13 l 1
160 | 24 24 l 1
161 | 24 36 m 1
162 | 35 36 l 1
163 | 35 25 l 1
164 | 24 25 l 1
165 | 24 36 l 1
166 | 24 48 m 1
167 | 35 48 l 1
168 | 35 37 l 1
169 | 24 37 l 1
170 | 24 48 l 1
171 | 24 60 m 1
172 | 35 60 l 1
173 | 35 49 l 1
174 | 24 49 l 1
175 | 24 60 l 1
176 | 24 72 m 1
177 | 35 72 l 1
178 | 35 61 l 1
179 | 24 61 l 1
180 | 24 72 l 1
181 | 24 84 m 1
182 | 35 84 l 1
183 | 35 73 l 1
184 | 24 73 l 1
185 | 24 84 l 1
186 | 24 96 m 1
187 | 35 96 l 1
188 | 35 85 l 1
189 | 24 85 l 1
190 | 24 96 l 1
191 | 24 108 m 1
192 | 35 108 l 1
193 | 35 97 l 1
194 | 24 97 l 1
195 | 24 108 l 1
196 | 12 12 m 1
197 | 23 12 l 1
198 | 23 1 l 1
199 | 12 1 l 1
200 | 12 12 l 1
201 | 12 24 m 1
202 | 23 24 l 1
203 | 23 13 l 1
204 | 12 13 l 1
205 | 12 24 l 1
206 | 12 36 m 1
207 | 23 36 l 1
208 | 23 25 l 1
209 | 12 25 l 1
210 | 12 36 l 1
211 | 12 48 m 1
212 | 23 48 l 1
213 | 23 37 l 1
214 | 12 37 l 1
215 | 12 48 l 1
216 | 12 60 m 1
217 | 23 60 l 1
218 | 23 49 l 1
219 | 12 49 l 1
220 | 12 60 l 1
221 | 12 72 m 1
222 | 23 72 l 1
223 | 23 61 l 1
224 | 12 61 l 1
225 | 12 72 l 1
226 | 12 84 m 1
227 | 23 84 l 1
228 | 23 73 l 1
229 | 12 73 l 1
230 | 12 84 l 1
231 | 12 96 m 1
232 | 23 96 l 1
233 | 23 85 l 1
234 | 12 85 l 1
235 | 12 96 l 1
236 | 12 108 m 1
237 | 23 108 l 1
238 | 23 97 l 1
239 | 12 97 l 1
240 | 12 108 l 1
241 | 0 12 m 1
242 | 11 12 l 1
243 | 11 1 l 1
244 | 0 1 l 1
245 | 0 12 l 1
246 | 0 24 m 1
247 | 11 24 l 1
248 | 11 13 l 1
249 | 0 13 l 1
250 | 0 24 l 1
251 | 0 36 m 1
252 | 11 36 l 1
253 | 11 25 l 1
254 | 0 25 l 1
255 | 0 36 l 1
256 | 0 48 m 1
257 | 11 48 l 1
258 | 11 37 l 1
259 | 0 37 l 1
260 | 0 48 l 1
261 | 0 60 m 1
262 | 11 60 l 1
263 | 11 49 l 1
264 | 0 49 l 1
265 | 0 60 l 1
266 | 0 72 m 1
267 | 11 72 l 1
268 | 11 61 l 1
269 | 0 61 l 1
270 | 0 72 l 1
271 | 0 84 m 1
272 | 11 84 l 1
273 | 11 73 l 1
274 | 0 73 l 1
275 | 0 84 l 1
276 | 0 96 m 1
277 | 11 96 l 1
278 | 11 85 l 1
279 | 0 85 l 1
280 | 0 96 l 1
281 | 0 108 m 1
282 | 11 108 l 1
283 | 11 97 l 1
284 | 0 97 l 1
285 | 0 108 l 1
286 | EndSplineSet
287 | EndChar
288 |
289 | StartChar: e
290 | Encoding: 101 101 1
291 | Width: 72
292 | Flags: HW
293 | LayerCount: 2
294 | Fore
295 | SplineSet
296 | 48 24 m 1
297 | 59 24 l 1
298 | 59 13 l 1
299 | 48 13 l 1
300 | 48 24 l 1
301 | 48 72 m 1
302 | 59 72 l 1
303 | 59 61 l 1
304 | 48 61 l 1
305 | 48 72 l 1
306 | 36 12 m 1
307 | 47 12 l 1
308 | 47 1 l 1
309 | 36 1 l 1
310 | 36 12 l 1
311 | 36 60 m 1
312 | 47 60 l 1
313 | 47 49 l 1
314 | 36 49 l 1
315 | 36 60 l 1
316 | 36 84 m 1
317 | 47 84 l 1
318 | 47 73 l 1
319 | 36 73 l 1
320 | 36 84 l 1
321 | 24 12 m 1
322 | 35 12 l 1
323 | 35 1 l 1
324 | 24 1 l 1
325 | 24 12 l 1
326 | 24 48 m 1
327 | 35 48 l 1
328 | 35 37 l 1
329 | 24 37 l 1
330 | 24 48 l 1
331 | 24 84 m 1
332 | 35 84 l 1
333 | 35 73 l 1
334 | 24 73 l 1
335 | 24 84 l 1
336 | 12 12 m 1
337 | 23 12 l 1
338 | 23 1 l 1
339 | 12 1 l 1
340 | 12 12 l 1
341 | 12 24 m 1
342 | 23 24 l 1
343 | 23 13 l 1
344 | 12 13 l 1
345 | 12 24 l 1
346 | 12 36 m 1
347 | 23 36 l 1
348 | 23 25 l 1
349 | 12 25 l 1
350 | 12 36 l 1
351 | 12 48 m 1
352 | 23 48 l 1
353 | 23 37 l 1
354 | 12 37 l 1
355 | 12 48 l 1
356 | 12 60 m 1
357 | 23 60 l 1
358 | 23 49 l 1
359 | 12 49 l 1
360 | 12 60 l 1
361 | 12 72 m 1
362 | 23 72 l 1
363 | 23 61 l 1
364 | 12 61 l 1
365 | 12 72 l 1
366 | 0 24 m 1
367 | 11 24 l 1
368 | 11 13 l 1
369 | 0 13 l 1
370 | 0 24 l 1
371 | 0 36 m 1
372 | 11 36 l 1
373 | 11 25 l 1
374 | 0 25 l 1
375 | 0 36 l 1
376 | 0 48 m 1
377 | 11 48 l 1
378 | 11 37 l 1
379 | 0 37 l 1
380 | 0 48 l 1
381 | 0 60 m 1
382 | 11 60 l 1
383 | 11 49 l 1
384 | 0 49 l 1
385 | 0 60 l 1
386 | EndSplineSet
387 | Validated: 1
388 | EndChar
389 |
390 | StartChar: y
391 | Encoding: 121 121 2
392 | Width: 72
393 | Flags: HW
394 | LayerCount: 2
395 | Fore
396 | SplineSet
397 | 48 48 m 1
398 | 59 48 l 1
399 | 59 37 l 1
400 | 48 37 l 1
401 | 48 48 l 1
402 | 48 60 m 1
403 | 59 60 l 1
404 | 59 49 l 1
405 | 48 49 l 1
406 | 48 60 l 1
407 | 36 36 m 1
408 | 47 36 l 1
409 | 47 25 l 1
410 | 36 25 l 1
411 | 36 36 l 1
412 | 24 24 m 1
413 | 35 24 l 1
414 | 35 13 l 1
415 | 24 13 l 1
416 | 24 24 l 1
417 | 24 36 m 1
418 | 35 36 l 1
419 | 35 25 l 1
420 | 24 25 l 1
421 | 24 36 l 1
422 | 12 12 m 1
423 | 23 12 l 1
424 | 23 1 l 1
425 | 12 1 l 1
426 | 12 12 l 1
427 | 12 48 m 1
428 | 23 48 l 1
429 | 23 37 l 1
430 | 12 37 l 1
431 | 12 48 l 1
432 | 0 12 m 1
433 | 11 12 l 1
434 | 11 1 l 1
435 | 0 1 l 1
436 | 0 12 l 1
437 | 0 60 m 1
438 | 11 60 l 1
439 | 11 49 l 1
440 | 0 49 l 1
441 | 0 60 l 1
442 | EndSplineSet
443 | Validated: 1
444 | EndChar
445 |
446 | StartChar: x
447 | Encoding: 120 120 3
448 | Width: 72
449 | Flags: HW
450 | LayerCount: 2
451 | Fore
452 | SplineSet
453 | 48 12 m 1
454 | 59 12 l 1
455 | 59 1 l 1
456 | 48 1 l 1
457 | 48 12 l 1
458 | 48 60 m 1
459 | 59 60 l 1
460 | 59 49 l 1
461 | 48 49 l 1
462 | 48 60 l 1
463 | 36 12 m 1
464 | 47 12 l 1
465 | 47 1 l 1
466 | 36 1 l 1
467 | 36 12 l 1
468 | 36 48 m 1
469 | 47 48 l 1
470 | 47 37 l 1
471 | 36 37 l 1
472 | 36 48 l 1
473 | 24 24 m 1
474 | 35 24 l 1
475 | 35 13 l 1
476 | 24 13 l 1
477 | 24 24 l 1
478 | 24 36 m 1
479 | 35 36 l 1
480 | 35 25 l 1
481 | 24 25 l 1
482 | 24 36 l 1
483 | 24 48 m 1
484 | 35 48 l 1
485 | 35 37 l 1
486 | 24 37 l 1
487 | 24 48 l 1
488 | 12 24 m 1
489 | 23 24 l 1
490 | 23 13 l 1
491 | 12 13 l 1
492 | 12 24 l 1
493 | 12 60 m 1
494 | 23 60 l 1
495 | 23 49 l 1
496 | 12 49 l 1
497 | 12 60 l 1
498 | 0 12 m 1
499 | 11 12 l 1
500 | 11 1 l 1
501 | 0 1 l 1
502 | 0 12 l 1
503 | 0 60 m 1
504 | 11 60 l 1
505 | 11 49 l 1
506 | 0 49 l 1
507 | 0 60 l 1
508 | EndSplineSet
509 | Validated: 1
510 | EndChar
511 |
512 | StartChar: pi
513 | Encoding: 960 960 4
514 | Width: 72
515 | Flags: HW
516 | LayerCount: 2
517 | Fore
518 | SplineSet
519 | 48 24 m 1
520 | 59 24 l 1
521 | 59 13 l 1
522 | 48 13 l 1
523 | 48 24 l 1
524 | 48 84 m 1
525 | 59 84 l 1
526 | 59 73 l 1
527 | 48 73 l 1
528 | 48 84 l 1
529 | 36 12 m 1
530 | 47 12 l 1
531 | 47 1 l 1
532 | 36 1 l 1
533 | 36 12 l 1
534 | 36 24 m 1
535 | 47 24 l 1
536 | 47 13 l 1
537 | 36 13 l 1
538 | 36 24 l 1
539 | 36 36 m 1
540 | 47 36 l 1
541 | 47 25 l 1
542 | 36 25 l 1
543 | 36 36 l 1
544 | 36 48 m 1
545 | 47 48 l 1
546 | 47 37 l 1
547 | 36 37 l 1
548 | 36 48 l 1
549 | 36 60 m 1
550 | 47 60 l 1
551 | 47 49 l 1
552 | 36 49 l 1
553 | 36 60 l 1
554 | 36 72 m 1
555 | 47 72 l 1
556 | 47 61 l 1
557 | 36 61 l 1
558 | 36 72 l 1
559 | 36 84 m 1
560 | 47 84 l 1
561 | 47 73 l 1
562 | 36 73 l 1
563 | 36 84 l 1
564 | 24 84 m 1
565 | 35 84 l 1
566 | 35 73 l 1
567 | 24 73 l 1
568 | 24 84 l 1
569 | 12 24 m 1
570 | 23 24 l 1
571 | 23 13 l 1
572 | 12 13 l 1
573 | 12 24 l 1
574 | 12 36 m 1
575 | 23 36 l 1
576 | 23 25 l 1
577 | 12 25 l 1
578 | 12 36 l 1
579 | 12 48 m 1
580 | 23 48 l 1
581 | 23 37 l 1
582 | 12 37 l 1
583 | 12 48 l 1
584 | 12 60 m 1
585 | 23 60 l 1
586 | 23 49 l 1
587 | 12 49 l 1
588 | 12 60 l 1
589 | 12 72 m 1
590 | 23 72 l 1
591 | 23 61 l 1
592 | 12 61 l 1
593 | 12 72 l 1
594 | 12 84 m 1
595 | 23 84 l 1
596 | 23 73 l 1
597 | 12 73 l 1
598 | 12 84 l 1
599 | 0 12 m 1
600 | 11 12 l 1
601 | 11 1 l 1
602 | 0 1 l 1
603 | 0 12 l 1
604 | 0 84 m 1
605 | 11 84 l 1
606 | 11 73 l 1
607 | 0 73 l 1
608 | 0 84 l 1
609 | EndSplineSet
610 | Validated: 1
611 | EndChar
612 |
613 | StartChar: uni03BC
614 | Encoding: 956 956 5
615 | Width: 72
616 | Flags: HW
617 | LayerCount: 2
618 | Fore
619 | SplineSet
620 | 48 24 m 1
621 | 59 24 l 1
622 | 59 13 l 1
623 | 48 13 l 1
624 | 48 24 l 1
625 | 48 36 m 1
626 | 59 36 l 1
627 | 59 25 l 1
628 | 48 25 l 1
629 | 48 36 l 1
630 | 36 48 m 1
631 | 47 48 l 1
632 | 47 37 l 1
633 | 36 37 l 1
634 | 36 48 l 1
635 | 36 60 m 1
636 | 47 60 l 1
637 | 47 49 l 1
638 | 36 49 l 1
639 | 36 60 l 1
640 | 36 72 m 1
641 | 47 72 l 1
642 | 47 61 l 1
643 | 36 61 l 1
644 | 36 72 l 1
645 | 36 84 m 1
646 | 47 84 l 1
647 | 47 73 l 1
648 | 36 73 l 1
649 | 36 84 l 1
650 | 24 36 m 1
651 | 35 36 l 1
652 | 35 25 l 1
653 | 24 25 l 1
654 | 24 36 l 1
655 | 12 48 m 1
656 | 23 48 l 1
657 | 23 37 l 1
658 | 12 37 l 1
659 | 12 48 l 1
660 | 12 60 m 1
661 | 23 60 l 1
662 | 23 49 l 1
663 | 12 49 l 1
664 | 12 60 l 1
665 | 12 72 m 1
666 | 23 72 l 1
667 | 23 61 l 1
668 | 12 61 l 1
669 | 12 72 l 1
670 | 12 84 m 1
671 | 23 84 l 1
672 | 23 73 l 1
673 | 12 73 l 1
674 | 12 84 l 1
675 | 0 12 m 1
676 | 11 12 l 1
677 | 11 1 l 1
678 | 0 1 l 1
679 | 0 12 l 1
680 | 0 24 m 1
681 | 11 24 l 1
682 | 11 13 l 1
683 | 0 13 l 1
684 | 0 24 l 1
685 | 0 36 m 1
686 | 11 36 l 1
687 | 11 25 l 1
688 | 0 25 l 1
689 | 0 36 l 1
690 | EndSplineSet
691 | EndChar
692 |
693 | StartChar: alpha
694 | Encoding: 945 945 6
695 | Width: 72
696 | Flags: HW
697 | LayerCount: 2
698 | Fore
699 | SplineSet
700 | 48 12 m 1
701 | 59 12 l 1
702 | 59 1 l 1
703 | 48 1 l 1
704 | 48 12 l 1
705 | 48 72 m 1
706 | 59 72 l 1
707 | 59 61 l 1
708 | 48 61 l 1
709 | 48 72 l 1
710 | 48 84 m 1
711 | 59 84 l 1
712 | 59 73 l 1
713 | 48 73 l 1
714 | 48 84 l 1
715 | 36 24 m 1
716 | 47 24 l 1
717 | 47 13 l 1
718 | 36 13 l 1
719 | 36 24 l 1
720 | 36 36 m 1
721 | 47 36 l 1
722 | 47 25 l 1
723 | 36 25 l 1
724 | 36 36 l 1
725 | 36 48 m 1
726 | 47 48 l 1
727 | 47 37 l 1
728 | 36 37 l 1
729 | 36 48 l 1
730 | 36 60 m 1
731 | 47 60 l 1
732 | 47 49 l 1
733 | 36 49 l 1
734 | 36 60 l 1
735 | 24 24 m 1
736 | 35 24 l 1
737 | 35 13 l 1
738 | 24 13 l 1
739 | 24 24 l 1
740 | 24 72 m 1
741 | 35 72 l 1
742 | 35 61 l 1
743 | 24 61 l 1
744 | 24 72 l 1
745 | 12 12 m 1
746 | 23 12 l 1
747 | 23 1 l 1
748 | 12 1 l 1
749 | 12 12 l 1
750 | 12 84 m 1
751 | 23 84 l 1
752 | 23 73 l 1
753 | 12 73 l 1
754 | 12 84 l 1
755 | 0 24 m 1
756 | 11 24 l 1
757 | 11 13 l 1
758 | 0 13 l 1
759 | 0 24 l 1
760 | 0 36 m 1
761 | 11 36 l 1
762 | 11 25 l 1
763 | 0 25 l 1
764 | 0 36 l 1
765 | 0 48 m 1
766 | 11 48 l 1
767 | 11 37 l 1
768 | 0 37 l 1
769 | 0 48 l 1
770 | 0 60 m 1
771 | 11 60 l 1
772 | 11 49 l 1
773 | 0 49 l 1
774 | 0 60 l 1
775 | 0 72 m 1
776 | 11 72 l 1
777 | 11 61 l 1
778 | 0 61 l 1
779 | 0 72 l 1
780 | EndSplineSet
781 | EndChar
782 |
783 | StartChar: epsilon
784 | Encoding: 949 949 7
785 | Width: 72
786 | Flags: HW
787 | LayerCount: 2
788 | Fore
789 | SplineSet
790 | 48 24 m 1
791 | 59 24 l 1
792 | 59 13 l 1
793 | 48 13 l 1
794 | 48 24 l 1
795 | 48 72 m 1
796 | 59 72 l 1
797 | 59 61 l 1
798 | 48 61 l 1
799 | 48 72 l 1
800 | 36 12 m 1
801 | 47 12 l 1
802 | 47 1 l 1
803 | 36 1 l 1
804 | 36 12 l 1
805 | 36 48 m 1
806 | 47 48 l 1
807 | 47 37 l 1
808 | 36 37 l 1
809 | 36 48 l 1
810 | 36 84 m 1
811 | 47 84 l 1
812 | 47 73 l 1
813 | 36 73 l 1
814 | 36 84 l 1
815 | 24 12 m 1
816 | 35 12 l 1
817 | 35 1 l 1
818 | 24 1 l 1
819 | 24 12 l 1
820 | 24 48 m 1
821 | 35 48 l 1
822 | 35 37 l 1
823 | 24 37 l 1
824 | 24 48 l 1
825 | 24 84 m 1
826 | 35 84 l 1
827 | 35 73 l 1
828 | 24 73 l 1
829 | 24 84 l 1
830 | 12 12 m 1
831 | 23 12 l 1
832 | 23 1 l 1
833 | 12 1 l 1
834 | 12 12 l 1
835 | 12 48 m 1
836 | 23 48 l 1
837 | 23 37 l 1
838 | 12 37 l 1
839 | 12 48 l 1
840 | 12 60 m 1
841 | 23 60 l 1
842 | 23 49 l 1
843 | 12 49 l 1
844 | 12 60 l 1
845 | 12 72 m 1
846 | 23 72 l 1
847 | 23 61 l 1
848 | 12 61 l 1
849 | 12 72 l 1
850 | 0 24 m 1
851 | 11 24 l 1
852 | 11 13 l 1
853 | 0 13 l 1
854 | 0 24 l 1
855 | 0 36 m 1
856 | 11 36 l 1
857 | 11 25 l 1
858 | 0 25 l 1
859 | 0 36 l 1
860 | EndSplineSet
861 | EndChar
862 |
863 | StartChar: Phi
864 | Encoding: 934 934 8
865 | Width: 72
866 | Flags: HW
867 | LayerCount: 2
868 | Fore
869 | SplineSet
870 | 48 48 m 1
871 | 59 48 l 1
872 | 59 37 l 1
873 | 48 37 l 1
874 | 48 48 l 1
875 | 48 60 m 1
876 | 59 60 l 1
877 | 59 49 l 1
878 | 48 49 l 1
879 | 48 60 l 1
880 | 48 72 m 1
881 | 59 72 l 1
882 | 59 61 l 1
883 | 48 61 l 1
884 | 48 72 l 1
885 | 36 36 m 1
886 | 47 36 l 1
887 | 47 25 l 1
888 | 36 25 l 1
889 | 36 36 l 1
890 | 36 84 m 1
891 | 47 84 l 1
892 | 47 73 l 1
893 | 36 73 l 1
894 | 36 84 l 1
895 | 36 96 m 1
896 | 47 96 l 1
897 | 47 85 l 1
898 | 36 85 l 1
899 | 36 96 l 1
900 | 36 108 m 1
901 | 47 108 l 1
902 | 47 97 l 1
903 | 36 97 l 1
904 | 36 108 l 1
905 | 24 36 m 1
906 | 35 36 l 1
907 | 35 25 l 1
908 | 24 25 l 1
909 | 24 36 l 1
910 | 24 48 m 1
911 | 35 48 l 1
912 | 35 37 l 1
913 | 24 37 l 1
914 | 24 48 l 1
915 | 24 60 m 1
916 | 35 60 l 1
917 | 35 49 l 1
918 | 24 49 l 1
919 | 24 60 l 1
920 | 24 72 m 1
921 | 35 72 l 1
922 | 35 61 l 1
923 | 24 61 l 1
924 | 24 72 l 1
925 | 24 84 m 1
926 | 35 84 l 1
927 | 35 73 l 1
928 | 24 73 l 1
929 | 24 84 l 1
930 | 12 12 m 1
931 | 23 12 l 1
932 | 23 1 l 1
933 | 12 1 l 1
934 | 12 12 l 1
935 | 12 24 m 1
936 | 23 24 l 1
937 | 23 13 l 1
938 | 12 13 l 1
939 | 12 24 l 1
940 | 12 36 m 1
941 | 23 36 l 1
942 | 23 25 l 1
943 | 12 25 l 1
944 | 12 36 l 1
945 | 12 84 m 1
946 | 23 84 l 1
947 | 23 73 l 1
948 | 12 73 l 1
949 | 12 84 l 1
950 | 0 48 m 1
951 | 11 48 l 1
952 | 11 37 l 1
953 | 0 37 l 1
954 | 0 48 l 1
955 | 0 60 m 1
956 | 11 60 l 1
957 | 11 49 l 1
958 | 0 49 l 1
959 | 0 60 l 1
960 | 0 72 m 1
961 | 11 72 l 1
962 | 11 61 l 1
963 | 0 61 l 1
964 | 0 72 l 1
965 | EndSplineSet
966 | EndChar
967 | EndChars
968 | EndSplineFont
969 |
--------------------------------------------------------------------------------
/www/js/main.js:
--------------------------------------------------------------------------------
1 | let changelog_visible = false
2 | let version;
3 | let versionCode;
4 | let selected_design
5 | let design_list
6 | let userLang = navigator.language || navigator.userLanguage;
7 | let is_electron = isElectron()
8 | let shell
9 | if(is_electron){
10 | shell = require("electron").shell;
11 | }
12 |
13 | import_custom_math(math)
14 |
15 | let global_logic_vars = {
16 | "active_input_handler": undefined,
17 | "next_align_id": 0,
18 | "next_subres_id": 0,
19 | "math_engine": math,
20 | "input_history": [],
21 | "mode_maps": {},
22 | "prefer_decimals": false,
23 | "calc_mode": "COMP",
24 | "rounding_mode": "Norm_1",
25 | "angle_mode": "Deg",
26 | "subres_functions" : {}
27 | }
28 |
29 | class UI{
30 | constructor(global_logic_vars){
31 | this.global_logic_vars = global_logic_vars
32 | this.userLang = userLang
33 | }
34 |
35 | async fetchChangelog(fdroid) {
36 | const langsToTry = [];
37 | // Normalize language code (e.g., en_US → en-US)
38 | const normalizedLang = userLang.replace('_', '-');
39 |
40 | // Break apart lang subtags (e.g., 'en-GB' → ['en-GB', 'en'])
41 | if (normalizedLang.includes('-')) {
42 | langsToTry.push(normalizedLang);
43 | langsToTry.push(normalizedLang.split('-')[0]);
44 | } else {
45 | langsToTry.push(normalizedLang);
46 | }
47 |
48 | // Always fall back to 'en-US'
49 | if (!langsToTry.includes('en-US')) {
50 | langsToTry.push('en-US');
51 | }
52 | if(!fdroid){
53 | langsToTry.push("commit_messages")
54 | }
55 |
56 | for (const lang of langsToTry) {
57 | const url = `changelog/${lang}.txt`;
58 | try {
59 | const response = await fetch(url);
60 | if (response.ok) {
61 | let text = await response.text();
62 |
63 | const lines = text.trim().split("\n");
64 |
65 | const changelog_content = document.getElementById("changelog-content")
66 | for(let line_index = 0; line_index < lines.length; line_index++){
67 | let line = lines[line_index]
68 | line = line.replaceAll("- - ","- ")
69 | let line_class;
70 |
71 | if(line.startsWith("- Feature: ")){
72 | line_class = "changelog-feature"
73 | }else if(line.startsWith("- Bugfix: ")){
74 | line_class = "changelog-bugfix"
75 | }else if(line.startsWith("- Tweak: ")){
76 | line_class = "changelog-tweak"
77 | }
78 |
79 | let line_element = document.createElement("span")
80 | if(line_class){
81 | line_element.classList.add(line_class)
82 | }
83 | line_element.innerText = line
84 | changelog_content.appendChild(line_element)
85 | }
86 | return
87 | }
88 | } catch (err) {
89 | // ignore and try next
90 | }
91 | }
92 |
93 | throw new Error('No changelog available in any language.');
94 | }
95 |
96 | toggle_changelog(){
97 | changelog_visible = !changelog_visible
98 | if(changelog_visible){
99 | document.getElementById("changelog").style.visibility = "visible"
100 | }else{
101 | document.getElementById("changelog").style.visibility = "hidden"
102 | }
103 | }
104 |
105 | setRootFontSize(size) {
106 | document.documentElement.style.fontSize = size + 'px';
107 | }
108 |
109 | handle_resize(){
110 | this.setRootFontSize(document.querySelector('[inkscape\\3a label="display_output"]').getBoundingClientRect().height * 0.034506 * 16.91331 / 2)
111 | this.global_logic_vars.active_input_handler.update_position()
112 | }
113 |
114 | vertical_align_elements() {
115 | // Step 1: Collect all the elements
116 | let alignRightElements = [...document.querySelectorAll('[class*="alignRight"]')];
117 |
118 | // Step 2: Sort them based on the 'align_id' in descending order
119 | alignRightElements.sort((a, b) => {
120 | const aId = a.className.match(/alignRight(\d+)/)[1];
121 | const bId = b.className.match(/alignRight(\d+)/)[1];
122 | return bId - aId; // Sorting in descending order
123 | });
124 |
125 | // Step 3: Process each element after sorting
126 | alignRightElements.forEach(el => {
127 | let align_id = el.className.match(/alignRight(\d+)/)[1];
128 | let align_left_el = document.getElementsByClassName("alignLeft" + align_id)[0];
129 | let align_left_rect = align_left_el.getBoundingClientRect()
130 |
131 | let right_y = el.getBoundingClientRect().top;
132 | let left_y = align_left_rect.top + align_left_rect.height / 2;
133 |
134 | let y_shift = right_y - left_y;
135 |
136 | el.parentElement.style.verticalAlign = `calc(${y_shift}px + 0.06rem)`;
137 | });
138 |
139 | this.scale_height_elements()
140 | }
141 |
142 | scale_height_elements() {
143 | let scale_height_elements = [...document.querySelectorAll('[class*="scale_height"]')];
144 |
145 | scale_height_elements.forEach(el => {
146 | let parent_element = el.parentElement
147 | let parent_rect = parent_element.getBoundingClientRect()
148 | let this_rect = el.getBoundingClientRect()
149 |
150 | let factor = parent_rect.height / this_rect.height
151 |
152 | el.style.transform = "scaleY(" + factor + ")";
153 | });
154 | }
155 |
156 | toggle_indicators(modes){
157 | for(let mode in modes){
158 | if(modes[mode]){
159 | document.querySelector('[inkscape\\3a label="indicator_' + mode + '"]').style.visibility = "visible"
160 | }else{
161 | document.querySelector('[inkscape\\3a label="indicator_' + mode + '"]').style.visibility = "hidden"
162 | }
163 | }
164 | }
165 |
166 | scroll_element(this_element){
167 | let cursor_x = document.getElementsByClassName("cursor")[0].getBoundingClientRect().right
168 | let scroll_border_x = document.querySelector('[inkscape\\3a label="scroll_x_border"]').getBoundingClientRect().left
169 | let x_dist_to_scroll_border = cursor_x - scroll_border_x
170 |
171 | let cursor_y_bot = document.getElementsByClassName("cursor")[0].getBoundingClientRect().bottom
172 | let scroll_border_y = document.querySelector('[inkscape\\3a label="scroll_y_border"]').getBoundingClientRect().top
173 | let y_dist_to_scroll_border = cursor_y_bot - scroll_border_y
174 |
175 | this_element.scrollBy(x_dist_to_scroll_border,y_dist_to_scroll_border)
176 | }
177 |
178 | align_element(displayElement, mathElement) {
179 | const rect = displayElement.getBoundingClientRect();
180 | mathElement.style.position = "absolute";
181 | mathElement.style.left = `${rect.left}px`;
182 | mathElement.style.top = `${rect.top}px`;
183 | mathElement.style.width = `${rect.width}px`;
184 | mathElement.style.height = `${rect.height}px`;
185 | }
186 |
187 | set_calc_mode(calc_mode){
188 | localStorage.setItem("calcMode", calc_mode);
189 | location.reload();
190 | }
191 |
192 | set_setup_setting(setting){
193 | switch(setting){
194 | case "Fix_0":
195 | case "Fix_1":
196 | case "Fix_2":
197 | case "Fix_3":
198 | case "Fix_4":
199 | case "Fix_5":
200 | case "Fix_6":
201 | case "Fix_7":
202 | case "Fix_8":
203 | case "Fix_9":
204 | case "Norm_1":
205 | case "Norm_2":
206 | localStorage.setItem("roundingMode", setting);
207 | location.reload();
208 | break;
209 |
210 | case "Deg":
211 | case "Rad":
212 | case "Gra":
213 | localStorage.setItem("angleMode", setting);
214 | location.reload();
215 | break;
216 | }
217 |
218 | }
219 | }
220 |
221 | let ui = new UI(global_logic_vars)
222 |
223 | async function fetch_version(){
224 | let res = await fetch("version.txt")
225 | let text = await res.text()
226 | version = text.trim()
227 |
228 | document.getElementById("version").innerText = "What's new in " + version + "?";
229 | document.getElementById("version-small").innerText = version;
230 | ui.fetchChangelog(version.endsWith(".0"))
231 | }
232 |
233 | async function fetch_versionCode(){
234 | let res = await fetch("versionCode.txt")
235 | let text = await res.text()
236 | versionCode = text.trim()
237 |
238 | const lastSeenVersionCode = localStorage.getItem("lastSeenVersionCode");
239 | if (lastSeenVersionCode !== versionCode) {
240 | ui.toggle_changelog(); // Show changelog automatically
241 | localStorage.setItem("lastSeenVersionCode", versionCode);
242 | }
243 | }
244 |
245 | async function fetch_design_list(){
246 | let res = await fetch("img/gui/list.json")
247 | let text = await res.text()
248 | design_list = JSON.parse(text)
249 |
250 | if(design_list.length > 1){
251 | design_list.forEach(design => {
252 | const design_option_div = document.createElement("div")
253 | design_option_div.classList.add("design-option")
254 | design_option_div.id = "design_" + design
255 | const preview_img = document.createElement("img")
256 | preview_img.src = "img/gui/" + design + ".svg"
257 | design_option_div.appendChild(preview_img)
258 | const name_label_div = document.createElement("div")
259 | name_label_div.classList.add("design-name-label")
260 | let design_name = design.split("_by_")[0]
261 | name_label_div.innerText = design_name
262 | design_option_div.appendChild(name_label_div)
263 | const author_label_div = document.createElement("div")
264 | author_label_div.classList.add("design-author-label")
265 | let design_author_name = design.split("_by_")[1]
266 | author_label_div.innerText = "by " + design_author_name
267 | design_option_div.appendChild(author_label_div)
268 | document.getElementById("design-select").appendChild(design_option_div)
269 | });
270 | }else{
271 | document.getElementById("design-select-label").style.display = "none"
272 | document.getElementById("design-select").style.display = "none"
273 | }
274 | }
275 |
276 | async function fetch_mode_maps(design){
277 | let res = await fetch("img/gui/" + design + ".json")
278 | let text = await res.text()
279 | let json_res = JSON.parse(text)
280 | global_logic_vars.mode_maps = json_res.mode_maps
281 | document.getElementById("math-input").style.color = json_res.font_color
282 | document.getElementById("math-output").style.color = json_res.font_color
283 | }
284 |
285 | document.addEventListener("DOMContentLoaded", async () => {
286 | await fetch_version()
287 | await fetch_versionCode()
288 | await fetch_design_list()
289 |
290 | const display = document.getElementById("display");
291 | const svgContainer = document.getElementById("svg-container");
292 |
293 | selected_design = localStorage.getItem("selectedDesign")
294 | if (!selected_design || !design_list.includes(selected_design)) {
295 | selected_design = "Classic_by_Joris Yidong Scholl"
296 | }
297 |
298 | await fetch_mode_maps(selected_design)
299 |
300 | // Load the SVG dynamically
301 | fetch("img/gui/" + selected_design + ".svg")
302 | .then(response => response.text())
303 | .then(data => {
304 | svgContainer.innerHTML = data;
305 |
306 | let decimal_separator = localStorage.getItem("decimalFormat")
307 | if (!decimal_separator) {
308 | decimal_separator = getDecimalSeparator()
309 | }
310 | document.getElementById("decimal-format-select").value = decimal_separator;
311 |
312 | global_logic_vars.prefer_decimals = (localStorage.getItem("preferDecimal") === "true")
313 | document.getElementById("prefer-decimal-input").checked = (localStorage.getItem("preferDecimal") === "true")
314 |
315 | let calc_mode = localStorage.getItem("calcMode")
316 | if (calc_mode) {
317 | global_logic_vars.calc_mode = calc_mode
318 | }
319 | switch(calc_mode){
320 | case "CMPLX":
321 | document.querySelector('[inkscape\\3a label="indicator_cmplx"]').style.visibility = "visible"
322 | break
323 | }
324 |
325 | let rounding_mode = localStorage.getItem("roundingMode")
326 | if (rounding_mode) {
327 | global_logic_vars.rounding_mode = rounding_mode
328 | }
329 | switch(rounding_mode){
330 | case "Fix_0":
331 | case "Fix_1":
332 | case "Fix_2":
333 | case "Fix_3":
334 | case "Fix_4":
335 | case "Fix_5":
336 | case "Fix_6":
337 | case "Fix_7":
338 | case "Fix_8":
339 | case "Fix_9":
340 | document.querySelector('[inkscape\\3a label="indicator_fix"]').style.visibility = "visible"
341 | break
342 | }
343 |
344 | let angle_mode = localStorage.getItem("angleMode")
345 | if (angle_mode) {
346 | global_logic_vars.angle_mode = angle_mode
347 | }
348 | switch(angle_mode){
349 | case "Deg":
350 | document.querySelector('[inkscape\\3a label="indicator_deg"]').style.visibility = "visible"
351 | break
352 |
353 | case "Rad":
354 | document.querySelector('[inkscape\\3a label="indicator_rad"]').style.visibility = "visible"
355 | break
356 |
357 | case "Gra":
358 | document.querySelector('[inkscape\\3a label="indicator_gra"]').style.visibility = "visible"
359 | break
360 | }
361 |
362 | if(design_list.length > 1){
363 | document.getElementById("design_" + selected_design).classList.add("design-selected")
364 | }
365 |
366 | if(decimal_separator == "."){
367 | userLang = "en-US"
368 | }else{
369 | userLang = "de-DE"
370 | }
371 |
372 | const lang_specific_elements = document.querySelectorAll('[inkscape\\3a label$="' + userLang + '"]');
373 |
374 | lang_specific_elements.forEach(element => {
375 | element.style.display = "inline"
376 | });
377 |
378 | new EquationSelectInputHandler(
379 | document.querySelector('[inkscape\\3a label="display_input"]'),
380 | document.getElementById("math-input"),
381 | document.querySelector('[inkscape\\3a label="display_output"]'),
382 | document.getElementById("math-output"),
383 | global_logic_vars,
384 | ui,
385 | userLang
386 | );
387 | ui.handle_resize()
388 | setTimeout(ui.handle_resize.bind(ui),1000)
389 | attachEventListeners();
390 | })
391 | .catch(error => console.error("Error loading SVG:", error));
392 |
393 | function attachEventListeners() {
394 | const designElements = document.querySelectorAll('.design-option')
395 |
396 | designElements.forEach(element => {
397 | element.addEventListener("pointerdown", function (e) {
398 | const design = e.currentTarget.id.substring("design_".length);
399 | localStorage.setItem("selectedDesign", design);
400 | location.reload();
401 | });
402 | });
403 |
404 | const keyElements = document.querySelectorAll('[inkscape\\3a label^="key_"]');
405 |
406 | keyElements.forEach(element => {
407 | element.addEventListener("pointerdown", function () {
408 | let input_code = this.getAttribute('inkscape:label')
409 | let key_code = input_code.substring("key_".length)
410 | let label_background_name = "label_background_" + key_code
411 | let label_background = document.querySelectorAll('[inkscape\\3a label="' + label_background_name + '"]');
412 | if(label_background.length != 0){
413 | label_background[0].classList.add("pressed");
414 | setTimeout(() => label_background[0].classList.remove("pressed"), 150);
415 | }
416 | global_logic_vars.input_history.push(input_code)
417 | global_logic_vars.active_input_handler.handle(input_code);
418 | });
419 | });
420 | window.addEventListener('resize', ui.handle_resize.bind(ui))
421 | document.getElementById("changelog").addEventListener('pointerdown', ui.toggle_changelog.bind(ui))
422 | document.getElementById("version-small").addEventListener('pointerdown', ui.toggle_changelog.bind(ui))
423 | document.getElementById("open-settings-btn").addEventListener('pointerdown', () => {
424 | document.getElementById("settings-menu").classList.toggle("hidden");
425 | })
426 | document.getElementById("close-settings-btn").addEventListener('pointerdown', () => {
427 | document.getElementById("settings-menu").classList.toggle("hidden");
428 | })
429 | document.getElementById("decimal-format-select").addEventListener("change", () => {
430 | const format = document.getElementById("decimal-format-select").value;
431 | localStorage.setItem("decimalFormat", format);
432 | location.reload();
433 | });
434 | document.getElementById("prefer-decimal-input").addEventListener("change", () => {
435 | global_logic_vars.prefer_decimals = document.getElementById("prefer-decimal-input").checked
436 | localStorage.setItem("preferDecimal", global_logic_vars.prefer_decimals);
437 | });
438 | if("cordova" in window){
439 | let a_elements = document.querySelectorAll("a")
440 | a_elements.forEach(a_element => {
441 | a_element.addEventListener("pointerdown", function (e) {
442 | var url = e.currentTarget.href;
443 | if (url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1) {
444 | e.preventDefault();
445 | cordova.InAppBrowser.open(url, '_system', 'hidden=yes,location=no');
446 | return false
447 | }
448 | });
449 | a_element.addEventListener("click", function (e) {
450 | e.preventDefault();
451 | });
452 | });
453 | }else if(is_electron){
454 | let a_elements = document.querySelectorAll("a")
455 | a_elements.forEach(a_element => {
456 | a_element.addEventListener("pointerdown", function (e) {
457 | var url = e.currentTarget.href;
458 | if (url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1) {
459 | e.preventDefault();
460 | shell.openExternal(url, '_blank');
461 | return false
462 | }
463 | });
464 | a_element.addEventListener("click", function (e) {
465 | e.preventDefault();
466 | });
467 | });
468 | }
469 | document.addEventListener("keydown",function(e){
470 | let key = e.key
471 | switch(key){
472 | case "0":
473 | case "1":
474 | case "2":
475 | case "3":
476 | case "4":
477 | case "5":
478 | case "6":
479 | case "7":
480 | case "8":
481 | case "9":
482 | case "+":
483 | case "-":
484 | case "(":
485 | case ")":
486 | global_logic_vars.active_input_handler.handle("key_" + key)
487 | break;
488 |
489 | case "*":
490 | global_logic_vars.active_input_handler.handle("key_x")
491 | break;
492 |
493 | case "/":
494 | global_logic_vars.active_input_handler.handle("key_÷")
495 | break;
496 |
497 | case "Enter":
498 | global_logic_vars.active_input_handler.handle("key_=")
499 | break;
500 |
501 | case "Delete":
502 | case "Backspace":
503 | global_logic_vars.active_input_handler.handle("key_del")
504 | break;
505 |
506 | case "Dead":
507 | global_logic_vars.active_input_handler.handle("key_pown")
508 | break;
509 |
510 | case "Tab":
511 | case " ":
512 | e.preventDefault()
513 | global_logic_vars.active_input_handler.handle("key_ac")
514 | break;
515 |
516 | case "x":
517 | case "X":
518 | global_logic_vars.active_input_handler.handle("key_uservar_X")
519 | break;
520 |
521 | case ".":
522 | case ",":
523 | global_logic_vars.active_input_handler.handle("key_comma")
524 | break;
525 |
526 | case "ArrowLeft":
527 | global_logic_vars.active_input_handler.handle("key_dir0")
528 | break;
529 |
530 | case "ArrowDown":
531 | global_logic_vars.active_input_handler.handle("key_dir1")
532 | break;
533 |
534 | case "ArrowRight":
535 | global_logic_vars.active_input_handler.handle("key_dir2")
536 | break;
537 |
538 | case "ArrowUp":
539 | global_logic_vars.active_input_handler.handle("key_dir3")
540 | break;
541 |
542 | case "Home":
543 | global_logic_vars.active_input_handler.handle("key_mode")
544 | break;
545 |
546 | case "F1":
547 | global_logic_vars.active_input_handler.handle("key_shift")
548 | break;
549 |
550 | case "F2":
551 | global_logic_vars.active_input_handler.handle("key_alpha")
552 | break;
553 |
554 | case "F3":
555 | global_logic_vars.active_input_handler.handle("key_mode")
556 | break;
557 |
558 | case "F4":
559 | global_logic_vars.active_input_handler.handle("key_on")
560 | break;
561 | }
562 | })
563 | }
564 | });
565 |
566 | function decodeHTMLEntities(str) {
567 | const txt = document.createElement('textarea');
568 | txt.innerHTML = str;
569 | return txt.value;
570 | }
571 |
572 | function log_calculation(){
573 | console.log({
574 | "userLang":userLang,
575 | "name":"INSERT_TEST_NAME",
576 | "input_history":global_logic_vars.input_history,
577 | "rendered_input":decodeHTMLEntities(global_logic_vars.active_input_handler.math_input_element.innerHTML),
578 | "rendered_output":decodeHTMLEntities(global_logic_vars.active_input_handler.math_output_element.innerHTML),
579 | "calc_mode": global_logic_vars.calc_mode,
580 | "rounding_mode": global_logic_vars.rounding_mode
581 | })
582 | }
583 |
584 | function isElectron() {
585 | // Renderer process
586 | if (typeof window !== 'undefined' && typeof window.process === 'object' && window.process.type === 'renderer') {
587 | return true;
588 | }
589 |
590 | // Main process
591 | if (typeof process !== 'undefined' && typeof process.versions === 'object' && !!process.versions.electron) {
592 | return true;
593 | }
594 |
595 | // Detect the user agent when the `nodeIntegration` option is set to true
596 | if (typeof navigator === 'object' && typeof navigator.userAgent === 'string' && navigator.userAgent.indexOf('Electron') >= 0) {
597 | return true;
598 | }
599 |
600 | return false;
601 | }
--------------------------------------------------------------------------------
/res/icon/android/foreground.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
15 |
21 |
27 |
33 |
35 |
39 |
43 |
44 |
52 |
58 |
59 |
63 |
67 |
71 |
75 |
79 |
83 |
87 |
91 |
95 |
99 |
103 |
107 |
111 |
115 |
119 |
123 |
127 |
131 |
135 |
139 |
143 |
147 |
151 |
155 |
159 |
163 |
167 |
171 |
175 |
179 |
183 |
187 |
191 |
195 |
199 |
203 |
207 |
211 |
215 |
219 |
223 |
227 |
231 |
235 |
239 |
243 |
247 |
251 |
255 |
259 |
263 |
267 |
271 |
275 |
279 |
283 |
284 |
285 |
--------------------------------------------------------------------------------