├── .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 | [![Check Fastlane](https://github.com/CardiJey/schulrechner/actions/workflows/fastlane.yml/badge.svg)](https://github.com/CardiJey/schulrechner/actions/workflows/fastlane.yml) 2 | [![NPM Tests](https://github.com/CardiJey/schulrechner/actions/workflows/npm_tests.yml/badge.svg)](https://github.com/CardiJey/schulrechner/actions/workflows/npm_tests.yml) 3 | 4 | 5 | 6 | 11 | 18 | 23 | 24 |
7 | 8 | Open in Browser 9 | 10 | 12 | 13 | Get it on F-Droid 16 | 17 | 19 | 20 | Get it on Flathub 21 | 22 |
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 | --------------------------------------------------------------------------------