├── .browserslistrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── angular.json ├── package-lock.json ├── package.json ├── scripts ├── compile-wasm.js └── wasm-cache-busting.js ├── src ├── .htaccess ├── app │ ├── app.component.css │ ├── app.component.html │ ├── app.component.ts │ ├── home │ │ ├── home.component.css │ │ ├── home.component.html │ │ └── home.component.ts │ ├── routes.ts │ └── wasm │ │ ├── 3d-cube │ │ ├── 3d-cube.c │ │ ├── 3d-cube.component.css │ │ ├── 3d-cube.component.html │ │ ├── 3d-cube.component.ts │ │ ├── build-cmd.js │ │ └── soil │ │ │ ├── SOIL.h │ │ │ └── libSOIL.bc │ │ ├── bmp-to-ascii │ │ ├── bmp-to-ascii.component.css │ │ ├── bmp-to-ascii.component.html │ │ ├── bmp-to-ascii.component.ts │ │ ├── bmp-to-ascii.cpp │ │ └── build-cmd.js │ │ ├── console-logger │ │ ├── build-cmd.js │ │ ├── console-logger.c │ │ ├── console-logger.component.html │ │ └── console-logger.component.ts │ │ ├── emscripten-wasm.component.ts │ │ ├── fibonacci │ │ ├── benchmark.ts │ │ ├── build-cmd.js │ │ ├── fibonacci.c │ │ ├── fibonacci.component.html │ │ ├── fibonacci.component.ts │ │ └── fibonacci.ts │ │ ├── humanize-time-pipe.ts │ │ ├── person-record │ │ ├── build-cmd.js │ │ ├── person-record.component.css │ │ ├── person-record.component.html │ │ ├── person-record.component.ts │ │ └── person-record.cpp │ │ ├── proof-of-work │ │ ├── build-cmd.js │ │ ├── proof-of-work.c │ │ ├── proof-of-work.component.css │ │ ├── proof-of-work.component.html │ │ ├── proof-of-work.component.ts │ │ ├── proof-of-work.emlib.js │ │ └── sha256 │ │ │ ├── sha256.c │ │ │ └── sha256.h │ │ ├── text-to-ascii │ │ ├── build-cmd.js │ │ ├── text-to-ascii.component.html │ │ ├── text-to-ascii.component.ts │ │ ├── text-to-ascii.cpp │ │ └── text-to-ascii.font.txt │ │ ├── tools.ts │ │ └── typings.d.ts ├── assets │ └── img │ │ ├── 3d-cube │ │ ├── angular.png │ │ ├── cat.png │ │ └── embroidery.png │ │ ├── angular-wasm.png │ │ ├── ascii │ │ ├── angular.bmp │ │ ├── heart.bmp │ │ └── hello.bmp │ │ └── person-record │ │ ├── age.svg │ │ ├── height.svg │ │ └── weight.svg ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── index.html ├── main.ts ├── polyfills.ts └── styles.css ├── tsconfig.app.json └── tsconfig.json /.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | last 2 Chrome versions 9 | last 2 ChromeAndroid versions 10 | 11 | last 2 Safari versions 12 | last 2 iOS versions 13 | 14 | last 2 Firefox versions 15 | last 2 FirefoxAndroid versions 16 | 17 | last 2 Edge versions -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | max_line_length = 120 11 | 12 | [*.md] 13 | max_line_length = off 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | src/assets 2 | src/app/wasm/**/*.emlib.js 3 | .eslintrc.js 4 | dist 5 | .angular 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | es6: true, 6 | node: true, 7 | }, 8 | extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"], 9 | parser: "@typescript-eslint/parser", 10 | parserOptions: { 11 | project: "tsconfig.json", 12 | sourceType: "module", 13 | }, 14 | plugins: ["@typescript-eslint"], 15 | overrides: [ 16 | { 17 | files: ["scripts/**/*.js"], 18 | rules: { 19 | "@typescript-eslint/no-var-requires": "off", 20 | }, 21 | }, 22 | ], 23 | }; 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /tmp 4 | /out-tsc 5 | /src/assets/wasm/*.* 6 | !/src/assets/wasm/.gitkeep 7 | /src/wasm-cache-busting.json 8 | 9 | # dependencies 10 | /node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.angular/cache 30 | /.sass-cache 31 | /connect.lock 32 | /coverage 33 | /libpeerconnection.log 34 | npm-debug.log 35 | testem.log 36 | /typings 37 | 38 | # e2e 39 | /e2e/*.js 40 | /e2e/*.map 41 | 42 | # System Files 43 | .DS_Store 44 | Thumbs.db 45 | 46 | # npm pack output 47 | *.tgz -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /src/assets/wasm 2 | /node_modules 3 | /dist 4 | /.angular 5 | 6 | *.md -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.defaultFormatter": "esbenp.prettier-vscode", 4 | "[javascript]": { 5 | "editor.defaultFormatter": "esbenp.prettier-vscode" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Boyan Mihaylov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular & WebAssembly 2 | 3 | This project shows how WebAssembly could be used in Angular in form of components and helper services. The examples are written in C/C++ and compiled to WebAssembly using [Emscripten](https://emscripten.org). 4 | 5 | You can find the following examples: 6 | 7 | - **Fibonacci** shows the "raw" communication between JavaScript and WebAssembly without using Emscripten's glue code. Inspired by [devlucky](https://hackernoon.com/how-to-get-a-performance-boost-using-webassembly-8844ec6dd665), the example demonstrates the performance difference between JavaScript and WebAssembly when calculating Fibonacci series using three different implementations. 8 | - **Console Logger** binds to window click directly in the C code by using Emscripten's library. The C code uses `printf`, which I have overloaded to add items to the list instead of printing them to the console (default behavior). 9 | - **Text-to-ASCII** allows you to convert text to ASCII art on the fly. 10 | - **BPM-to-ASCII** allows you to convert simple bitmaps to ASCII art. 11 | - **3D Cube** shows how you can render 3D graphics using OpenGL (which is then converted to WebGL) and manipulate it on the fly. 12 | - **Proof of Work** is a simple [Proof of Work](https://en.bitcoin.it/wiki/Proof_of_work) system (similar to the one used in bitcoin), which demonstrates activities that might take long time to complete. 13 | - **Person Record** shows how to pass complex data between JavaScript and WebAssembly. 14 | 15 | ## Build 16 | 17 | You need Docker installed on your machine to compile the C/C++ examples to WebAssembly. 18 | 19 | To build the demo locally run: 20 | 21 | ```bash 22 | npm i 23 | npm run wasm 24 | npm start 25 | ``` 26 | 27 | To build a deployable artifact, simply replace the last command with 28 | 29 | ```bash 30 | npm run build 31 | ``` 32 | 33 | Then you can open your browser at `http://localhost:4200` to see it. 34 | 35 | ### Pre-compiled dependencies 36 | 37 | For some of the examples, I have pre-compiled parts of the C/C++ source into linked bitcode (_.bc_ files) to ease the build process. 38 | 39 | - [libSOIL](https://github.com/boyanio/SOIL-wasm) - Simple OpenGL Image Library (SOIL) is a tiny C library used primarily for uploading textures into OpenGL 40 | 41 | ## Questions & contribution 42 | 43 | You can reach me on Twitter [@boyanio](https://twitter.com/boyanio). You can also open an issue here on GitHub or make a Pull-Request directly. 44 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "cli": { 4 | "analytics": false 5 | }, 6 | "version": 1, 7 | "newProjectRoot": "projects", 8 | "projects": { 9 | "angular-wasm": { 10 | "projectType": "application", 11 | "schematics": {}, 12 | "root": "", 13 | "sourceRoot": "src", 14 | "prefix": "app", 15 | "architect": { 16 | "build": { 17 | "builder": "@angular/build:application", 18 | "options": { 19 | "outputPath": { 20 | "base": "dist" 21 | }, 22 | "index": "src/index.html", 23 | "polyfills": ["src/polyfills.ts"], 24 | "tsConfig": "tsconfig.app.json", 25 | "assets": ["src/assets", "src/.htaccess"], 26 | "styles": ["node_modules/ladda/dist/ladda.min.css", "src/styles.css"], 27 | "scripts": [], 28 | "extractLicenses": false, 29 | "sourceMap": true, 30 | "optimization": false, 31 | "namedChunks": true, 32 | "browser": "src/main.ts" 33 | }, 34 | "configurations": { 35 | "production": { 36 | "fileReplacements": [ 37 | { 38 | "replace": "src/environments/environment.ts", 39 | "with": "src/environments/environment.prod.ts" 40 | } 41 | ], 42 | "optimization": true, 43 | "outputHashing": "all", 44 | "sourceMap": false, 45 | "namedChunks": false, 46 | "extractLicenses": true, 47 | "baseHref": "/angular-wasm/" 48 | } 49 | }, 50 | "defaultConfiguration": "" 51 | }, 52 | "serve": { 53 | "builder": "@angular/build:dev-server", 54 | "options": { 55 | "open": true, 56 | "buildTarget": "angular-wasm:build" 57 | }, 58 | "configurations": { 59 | "production": { 60 | "buildTarget": "angular-wasm:build:production" 61 | } 62 | } 63 | } 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-wasm", 3 | "version": "0.1.0", 4 | "description": "A collection of components and services for Angular to demonstrate integration with WebAssembly", 5 | "license": "MIT", 6 | "scripts": { 7 | "ng": "ng", 8 | "build": "ng build --configuration production", 9 | "wasm": "node scripts/compile-wasm.js && node scripts/wasm-cache-busting.js", 10 | "start": "ng serve", 11 | "lint": "eslint .", 12 | "prettier:check": "prettier --check .", 13 | "prettier:write": "prettier --check . --write" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/boyanio/angular-wasm.git" 18 | }, 19 | "keywords": [ 20 | "Angular", 21 | "WebAssembly", 22 | "JavaScript" 23 | ], 24 | "private": false, 25 | "author": "Boyan Mihaylov", 26 | "devDependencies": { 27 | "@angular/build": "^19.2.4", 28 | "@angular/cli": "^19.2.4", 29 | "@angular/common": "^19.2.3", 30 | "@angular/compiler": "^19.2.3", 31 | "@angular/compiler-cli": "^19.2.3", 32 | "@angular/core": "^19.2.3", 33 | "@angular/forms": "^19.2.3", 34 | "@angular/platform-browser": "^19.2.3", 35 | "@angular/platform-browser-dynamic": "^19.2.3", 36 | "@angular/router": "^19.2.3", 37 | "@typescript-eslint/eslint-plugin": "^6.18.1", 38 | "@typescript-eslint/parser": "^6.18.1", 39 | "angular2-ladda": "^5.0.0", 40 | "chalk": "^4.0.0", 41 | "eslint": "^8.41.0", 42 | "eslint-config-prettier": "^8.8.0", 43 | "prettier": "^2.8.8", 44 | "rxjs": "^6.5.4", 45 | "typescript": "^5.5.4", 46 | "zone.js": "^0.15.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /scripts/compile-wasm.js: -------------------------------------------------------------------------------- 1 | const { execSync } = require("child_process"); 2 | const fs = require("fs"); 3 | const path = require("path"); 4 | const chalk = require("chalk"); 5 | 6 | const rootDir = path.resolve(__dirname, "../"); 7 | 8 | const ensureWasmOutputDirExists = () => { 9 | console.log("Preparing wasm output folder...\n"); 10 | 11 | const src = path.resolve(rootDir, "src/assets/wasm"); 12 | if (fs.existsSync(src)) { 13 | const files = fs.readdirSync(src); 14 | for (let file of files) { 15 | fs.unlinkSync(path.join(src, file)); 16 | } 17 | } else { 18 | fs.mkdirSync(src); 19 | } 20 | }; 21 | 22 | const compileWasmSources = () => { 23 | console.log("Compiling wasm sources..."); 24 | 25 | const wasmDir = path.resolve(rootDir, "src/app/wasm"); 26 | for (let item of fs.readdirSync(wasmDir)) { 27 | const itemPath = path.join(wasmDir, item); 28 | if (!fs.lstatSync(itemPath).isDirectory()) { 29 | continue; 30 | } 31 | 32 | const buildFilePath = path.join(itemPath, "build-cmd.js"); 33 | const { cmd } = require(buildFilePath); 34 | 35 | // fix for Windows 36 | const formattedCmd = cmd.replace("$(pwd)", rootDir); 37 | 38 | console.log("\nCompiling wasm source for", chalk.green(item)); 39 | console.log(formattedCmd); 40 | execSync(formattedCmd, { cwd: rootDir, stdio: "inherit" }); 41 | } 42 | }; 43 | 44 | ensureWasmOutputDirExists(); 45 | compileWasmSources(); 46 | console.log("\nAll sources have been successfully compiled."); 47 | -------------------------------------------------------------------------------- /scripts/wasm-cache-busting.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This script generates hash for the Emscripten-generated files (js and wasm), 3 | * so that the client can fetch the correct one without cache issues. 4 | */ 5 | const crypto = require("crypto"); 6 | const path = require("path"); 7 | const fs = require("fs"); 8 | 9 | const rootDir = path.resolve(__dirname, "../"); 10 | 11 | const generateChecksum = (str, algorithm, encoding) => 12 | crypto 13 | .createHash(algorithm || "md5") 14 | .update(str, "utf8") 15 | .digest(encoding || "hex"); 16 | 17 | const result = {}; 18 | 19 | const wasmDir = path.resolve(rootDir, "src/assets/wasm"); 20 | for (let item of fs.readdirSync(wasmDir)) { 21 | const itemPath = path.join(wasmDir, item); 22 | if (!fs.lstatSync(itemPath).isFile()) { 23 | continue; 24 | } 25 | 26 | const content = fs.readFileSync(itemPath, { encoding: "utf-8" }); 27 | result[item] = generateChecksum(content); 28 | } 29 | 30 | const cacheBustingFilePath = path.join(rootDir, "src/wasm-cache-busting.json"); 31 | fs.writeFileSync(cacheBustingFilePath, JSON.stringify(result, null, 2)); 32 | -------------------------------------------------------------------------------- /src/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | RewriteBase /angular-wasm/ 3 | Options +FollowSymLinks 4 | 5 | RewriteCond %{REQUEST_FILENAME} !-f 6 | RewriteRule ^(.*)$ index.html [L,QSA] -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- 1 | .jumbotron { 2 | background-color: #18bc9c; 3 | } 4 | 5 | .jumbotron .lead { 6 | color: #fff; 7 | font-size: 1.5em; 8 | } 9 | 10 | .jumbotron a:focus, 11 | .jumbotron a:active, 12 | .jumbotron a:hover { 13 | color: #fff; 14 | } 15 | 16 | .jumbotron img { 17 | max-width: 200px; 18 | } 19 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | Fork me on GitHub 8 | 9 | 10 |
11 |
12 |

Angular & WebAssembly

13 |

A collection of examples of how WebAssembly can be used with Angular

14 |

15 | Angular & WebAssembly logos 16 |

17 |

18 | Home 19 | 20 | GitHub 22 | 23 | Twitter 25 |

26 |
27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { RouterModule } from "@angular/router"; 3 | 4 | @Component({ 5 | selector: "app-root", 6 | templateUrl: "./app.component.html", 7 | styleUrls: ["./app.component.css"], 8 | imports: [RouterModule], 9 | }) 10 | export class AppComponent {} 11 | -------------------------------------------------------------------------------- /src/app/home/home.component.css: -------------------------------------------------------------------------------- 1 | .portfolio { 2 | margin-bottom: -15px; 3 | } 4 | 5 | .portfolio .portfolio-item { 6 | position: relative; 7 | display: block; 8 | max-width: 25rem; 9 | margin-bottom: 15px; 10 | display: block; 11 | text-align: center; 12 | font-size: 1.5rem; 13 | height: 150px; 14 | line-height: 150px; 15 | border: 2px dotted #18bc9c; 16 | cursor: pointer; 17 | } 18 | 19 | .portfolio .portfolio-item:hover { 20 | color: #fff; 21 | -webkit-transition: all ease 0.5s; 22 | -moz-transition: all ease 0.5s; 23 | transition: all ease 0.5s; 24 | background-color: #18bc9c; 25 | text-decoration: none; 26 | } 27 | -------------------------------------------------------------------------------- /src/app/home/home.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | {{ demo.name }} 6 |
7 |
8 |
9 |
10 | -------------------------------------------------------------------------------- /src/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { NgForOf } from "@angular/common"; 2 | import { Component } from "@angular/core"; 3 | import { Router, RouterModule } from "@angular/router"; 4 | 5 | export interface Demo { 6 | name: string; 7 | routerLink: string; 8 | } 9 | 10 | @Component({ 11 | selector: "app-home", 12 | templateUrl: "./home.component.html", 13 | styleUrls: ["./home.component.css"], 14 | imports: [RouterModule, NgForOf], 15 | }) 16 | export class HomeComponent { 17 | demos: Demo[]; 18 | 19 | constructor(router: Router) { 20 | this.demos = router.config 21 | .filter((r) => r.data && r.data.demo) 22 | .map((r) => ({ 23 | name: r.data.name, 24 | routerLink: r.path, 25 | })); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/app/routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from "@angular/router"; 2 | 3 | import { HomeComponent } from "./home/home.component"; 4 | import { WasmFibonacciComponent } from "./wasm/fibonacci/fibonacci.component"; 5 | import { WasmConsoleLoggerComponent } from "./wasm/console-logger/console-logger.component"; 6 | import { WasmTextToAsciiComponent } from "./wasm/text-to-ascii/text-to-ascii.component"; 7 | import { WasmBmpToAsciiComponent } from "./wasm/bmp-to-ascii/bmp-to-ascii.component"; 8 | import { Wasm3dCubeComponent } from "./wasm/3d-cube/3d-cube.component"; 9 | import { WasmProofOfWorkComponent } from "./wasm/proof-of-work/proof-of-work.component"; 10 | import { WasmPersonRecordComponent } from "./wasm/person-record/person-record.component"; 11 | 12 | export const routes: Routes = [ 13 | { path: "", component: HomeComponent }, 14 | { 15 | path: "fibonacci", 16 | component: WasmFibonacciComponent, 17 | data: { demo: true, name: "Fibonacci battlefield" }, 18 | }, 19 | { 20 | path: "console-logger", 21 | component: WasmConsoleLoggerComponent, 22 | data: { demo: true, name: "Console logger" }, 23 | }, 24 | { 25 | path: "text-to-ascii", 26 | component: WasmTextToAsciiComponent, 27 | data: { demo: true, name: "Text to ASCII art converter" }, 28 | }, 29 | { 30 | path: "bmp-to-ascii", 31 | component: WasmBmpToAsciiComponent, 32 | data: { demo: true, name: "Bitmap to ASCII art converter" }, 33 | }, 34 | { path: "3d-cube", component: Wasm3dCubeComponent, data: { demo: true, name: "3D cube" } }, 35 | { 36 | path: "proof-of-work", 37 | component: WasmProofOfWorkComponent, 38 | data: { demo: true, name: "Proof of work" }, 39 | }, 40 | { 41 | path: "person-record", 42 | component: WasmPersonRecordComponent, 43 | data: { demo: true, name: "Person Record" }, 44 | }, 45 | ] as const; 46 | -------------------------------------------------------------------------------- /src/app/wasm/3d-cube/3d-cube.c: -------------------------------------------------------------------------------- 1 | /* Code modified from http://www.lousodrome.net/opengl/ */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define SIZE 256 11 | 12 | static int window_width = 500; 13 | static int window_height = 500; 14 | 15 | /* 16 | ** Just a textured cube 17 | */ 18 | void Cube(void) { 19 | glBegin(GL_QUADS); 20 | 21 | glTexCoord2i(0, 0); glVertex3f(-1, -1, -1); 22 | glTexCoord2i(0, 1); glVertex3f(-1, -1, 1); 23 | glTexCoord2i(1, 1); glVertex3f(-1, 1, 1); 24 | glTexCoord2i(1, 0); glVertex3f(-1, 1, -1); 25 | 26 | glTexCoord2i(0, 0); glVertex3f(1, -1, -1); 27 | glTexCoord2i(0, 1); glVertex3f(1, -1, 1); 28 | glTexCoord2i(1, 1); glVertex3f(1, 1, 1); 29 | glTexCoord2i(1, 0); glVertex3f(1, 1, -1); 30 | 31 | glTexCoord2i(0, 0); glVertex3f(-1, -1, -1); 32 | glTexCoord2i(0, 1); glVertex3f(-1, -1, 1); 33 | glTexCoord2i(1, 1); glVertex3f(1, -1, 1); 34 | glTexCoord2i(1, 0); glVertex3f(1, -1, -1); 35 | 36 | glTexCoord2i(0, 0); glVertex3f(-1, 1, -1); 37 | glTexCoord2i(0, 1); glVertex3f(-1, 1, 1); 38 | glTexCoord2i(1, 1); glVertex3f(1, 1, 1); 39 | glTexCoord2i(1, 0); glVertex3f(1, 1, -1); 40 | 41 | glTexCoord2i(0, 0); glVertex3f(-1, -1, -1); 42 | glTexCoord2i(0, 1); glVertex3f(-1, 1, -1); 43 | glTexCoord2i(1, 1); glVertex3f(1, 1, -1); 44 | glTexCoord2i(1, 0); glVertex3f(1, -1, -1); 45 | 46 | glTexCoord2i(0, 0); glVertex3f(-1, -1, 1); 47 | glTexCoord2i(0, 1); glVertex3f(-1, 1, 1); 48 | glTexCoord2i(1, 1); glVertex3f(1, 1, 1); 49 | glTexCoord2i(1, 0); glVertex3f(1, -1, 1); 50 | 51 | glEnd(); 52 | } 53 | 54 | /* 55 | ** Function called to update rendering 56 | */ 57 | void DisplayFunc(void) { 58 | static float alpha = 20; 59 | 60 | glLoadIdentity(); 61 | glTranslatef(0, 0, -10); 62 | glRotatef(30, 1, 0, 0); 63 | glRotatef(alpha, 0, 1, 0); 64 | 65 | /* Define a view-port adapted to the texture */ 66 | glMatrixMode(GL_PROJECTION); 67 | glLoadIdentity(); 68 | gluPerspective(20, 1, 5, 15); 69 | glViewport(0, 0, SIZE, SIZE); 70 | glMatrixMode(GL_MODELVIEW); 71 | 72 | /* Render to buffer */ 73 | glClearColor(1, 1, 1, 0); 74 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 75 | Cube(); 76 | glFlush(); 77 | 78 | /* Render to screen */ 79 | glMatrixMode(GL_PROJECTION); 80 | glLoadIdentity(); 81 | gluPerspective(20, window_width / (float)window_height, 5, 15); 82 | glViewport(0, 0, window_width, window_height); 83 | glMatrixMode(GL_MODELVIEW); 84 | glClearColor(1, 1, 1, 0); 85 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 86 | Cube(); 87 | 88 | /* End */ 89 | glFlush(); 90 | glutSwapBuffers(); 91 | 92 | /* Update again and again */ 93 | alpha = alpha + 0.1; 94 | glutPostRedisplay(); 95 | } 96 | 97 | /* 98 | ** Function called when the window is created or resized 99 | */ 100 | void ReshapeFunc(int width, int height) { 101 | window_width = width; 102 | window_height = height; 103 | glutPostRedisplay(); 104 | } 105 | 106 | #ifndef __EMSCRIPTEN__ 107 | 108 | /* 109 | ** Function called when a key is hit 110 | */ 111 | void KeyboardFunc(unsigned char key, int x, int y) { 112 | int foo; 113 | 114 | foo = x + y; /* Has no effect: just to avoid a warning */ 115 | if ('q' == key || 'Q' == key || 27 == key) 116 | exit(0); 117 | } 118 | 119 | #endif // !__EMSCRIPTEN__ 120 | 121 | EMSCRIPTEN_KEEPALIVE 122 | void set_texture(const char* fileName) { 123 | int texture_width, texture_height; 124 | unsigned char* texture = SOIL_load_image(fileName, &texture_width, &texture_height, 0, SOIL_LOAD_RGB); 125 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texture_width, texture_height, 0, GL_RGB, GL_UNSIGNED_BYTE, texture); 126 | SOIL_free_image_data(texture); 127 | } 128 | 129 | int main(int argc, char **argv) { 130 | 131 | if (argc != 2) { 132 | printf("You should supply one and only one parameter: the initial texture image\n"); 133 | return -1; 134 | } 135 | 136 | /* Creation of the window */ 137 | glutInit(&argc, argv); 138 | glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH); 139 | glutInitWindowSize(500, 500); 140 | glutCreateWindow("Render to texture"); 141 | 142 | /* OpenGL settings */ 143 | glEnable(GL_DEPTH_TEST); 144 | 145 | /* Texture setting */ 146 | unsigned int texture_id; 147 | glEnable(GL_TEXTURE_2D); 148 | glGenTextures(1, &texture_id); 149 | glBindTexture(GL_TEXTURE_2D, texture_id); 150 | 151 | /* Load and apply the texture */ 152 | set_texture(argv[1]); 153 | 154 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 155 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 156 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 157 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 158 | 159 | /* Declaration of the callbacks */ 160 | glutDisplayFunc(&DisplayFunc); 161 | glutReshapeFunc(&ReshapeFunc); 162 | #ifndef __EMSCRIPTEN__ 163 | glutKeyboardFunc(&KeyboardFunc); 164 | #endif // !__EMSCRIPTEN__ 165 | 166 | /* Loop */ 167 | glutMainLoop(); 168 | 169 | /* Never reached */ 170 | return 0; 171 | } -------------------------------------------------------------------------------- /src/app/wasm/3d-cube/3d-cube.component.css: -------------------------------------------------------------------------------- 1 | canvas.cube { 2 | border: 0px none; 3 | } 4 | 5 | .img-thumbnail { 6 | cursor: pointer; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/wasm/3d-cube/3d-cube.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

3D cube

4 |

This example shows how you can render 3D graphics using WebGL and manipulate it on the fly.

5 |
6 | 7 |
8 |
9 |
10 |
11 |
Choose one of the predefined images
12 |
13 |
14 | 3D cube predefined image 20 |
21 |
22 |
23 |
24 |
Upload your own image
25 |
26 |
27 |
28 |
29 | 35 | 36 |
37 |
38 |
39 |
40 |
41 |
42 |
Fullscreen
43 |
44 |
45 | 46 |
47 |
48 |
49 |
50 | 51 |
52 |
53 |
54 |
55 |
56 | -------------------------------------------------------------------------------- /src/app/wasm/3d-cube/3d-cube.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewChild, ElementRef, NgZone } from "@angular/core"; 2 | import { HttpClient } from "@angular/common/http"; 3 | import { EmscriptenWasmComponent } from "../emscripten-wasm.component"; 4 | import { NgFor, NgIf } from "@angular/common"; 5 | 6 | const getFileName = (filePath: string) => filePath.split("/").reverse()[0]; 7 | 8 | const allowedMimeTypes = ["image/bmp", "image/x-windows-bmp", "image/jpeg", "image/pjpeg", "image/png"]; 9 | 10 | const defaultImage = "assets/img/3d-cube/angular.png"; 11 | 12 | const requestFullscreen = 13 | document.documentElement.requestFullscreen || 14 | document.documentElement["webkitRequestFullscreen"] || 15 | document.documentElement["msRequestFullscreen"] || 16 | document.documentElement["mozRequestFullScreen"]; 17 | 18 | @Component({ 19 | templateUrl: "./3d-cube.component.html", 20 | styleUrls: ["./3d-cube.component.css"], 21 | imports: [NgIf, NgFor], 22 | }) 23 | export class Wasm3dCubeComponent extends EmscriptenWasmComponent { 24 | @ViewChild("canvas") canvas: ElementRef; 25 | predefinedImages: string[]; 26 | error: string; 27 | fileUploadAccept: string; 28 | supportsFullscreen: boolean; 29 | 30 | constructor(private httpClient: HttpClient, private ngZone: NgZone) { 31 | super("Cube3dModule", "3d-cube.js"); 32 | 33 | this.supportsFullscreen = !!requestFullscreen; 34 | this.fileUploadAccept = allowedMimeTypes.join(","); 35 | this.predefinedImages = [defaultImage, "assets/img/3d-cube/cat.png", "assets/img/3d-cube/embroidery.png"]; 36 | 37 | this.moduleDecorator = (mod) => { 38 | mod.arguments = [getFileName(defaultImage)]; 39 | mod.preRun = [ 40 | () => { 41 | mod.FS_createPreloadedFile("/", getFileName(defaultImage), defaultImage, true); 42 | }, 43 | ]; 44 | mod.canvas = this.canvas.nativeElement; 45 | mod.printErr = (what: string) => { 46 | if (!what.startsWith("WARNING")) { 47 | this.ngZone.run(() => (this.error = what)); 48 | } 49 | }; 50 | }; 51 | } 52 | 53 | toggleFullscreen() { 54 | if (requestFullscreen) { 55 | requestFullscreen.bind(this.canvas.nativeElement)(); 56 | } 57 | } 58 | 59 | selectPredefinedImage(index: number) { 60 | this.error = null; 61 | 62 | const imageUrl: string = this.predefinedImages[index]; 63 | this.httpClient 64 | .get(imageUrl, { responseType: "arraybuffer" }) 65 | .subscribe((imageBytes) => this.setTexture(getFileName(imageUrl), new Uint8Array(imageBytes))); 66 | } 67 | 68 | onFileUploaded(files: FileList) { 69 | if (!files.length) { 70 | return; 71 | } 72 | 73 | this.error = null; 74 | 75 | const file = files[0]; 76 | if (allowedMimeTypes.indexOf(file.type) < 0) { 77 | this.error = `Unsupported mime type ${file.type}`; 78 | return; 79 | } 80 | 81 | const fileName = file.name; 82 | 83 | const reader = new FileReader(); 84 | reader.onload = () => { 85 | const inputArray = new Uint8Array(reader.result); 86 | this.setTexture(fileName, inputArray); 87 | }; 88 | reader.readAsArrayBuffer(file); 89 | } 90 | 91 | private setTexture(fileName: string, inputArray: Uint8Array) { 92 | const isDefaultImage = fileName === getFileName(defaultImage); 93 | 94 | // Default image is always there 95 | if (!isDefaultImage) { 96 | this.module.FS_createDataFile("/", fileName, inputArray, true); 97 | } 98 | 99 | this.module.ccall("set_texture", "void", ["string"], [fileName]); 100 | 101 | // Delete the file afterwards to free memory 102 | if (!isDefaultImage) { 103 | this.module.FS_unlink(fileName); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/app/wasm/3d-cube/build-cmd.js: -------------------------------------------------------------------------------- 1 | exports.cmd = 2 | 'docker run --rm -v $(pwd):/src emscripten/emsdk emcc -Os src/app/wasm/3d-cube/soil/libSOIL.bc src/app/wasm/3d-cube/3d-cube.c -o src/assets/wasm/3d-cube.js -s LEGACY_GL_EMULATION=1 -Isrc/app/wasm/3d-cube/soil -s EXTRA_EXPORTED_RUNTIME_METHODS="[\'ccall\']" -s FORCE_FILESYSTEM=1 -s MODULARIZE=1 -s EXPORT_NAME="Cube3dModule"'; 3 | -------------------------------------------------------------------------------- /src/app/wasm/3d-cube/soil/SOIL.h: -------------------------------------------------------------------------------- 1 | /** 2 | @mainpage SOIL 3 | 4 | Jonathan Dummer 5 | 2007-07-26-10.36 6 | 7 | Simple OpenGL Image Library 8 | 9 | A tiny c library for uploading images as 10 | textures into OpenGL. Also saving and 11 | loading of images is supported. 12 | 13 | I'm using Sean's Tool Box image loader as a base: 14 | http://www.nothings.org/ 15 | 16 | I'm upgrading it to load TGA and DDS files, and a direct 17 | path for loading DDS files straight into OpenGL textures, 18 | when applicable. 19 | 20 | Image Formats: 21 | - BMP load & save 22 | - TGA load & save 23 | - DDS load & save 24 | - PNG load 25 | - JPG load 26 | 27 | OpenGL Texture Features: 28 | - resample to power-of-two sizes 29 | - MIPmap generation 30 | - compressed texture S3TC formats (if supported) 31 | - can pre-multiply alpha for you, for better compositing 32 | - can flip image about the y-axis (except pre-compressed DDS files) 33 | 34 | Thanks to: 35 | * Sean Barret - for the awesome stb_image 36 | * Dan Venkitachalam - for finding some non-compliant DDS files, and patching some explicit casts 37 | * everybody at gamedev.net 38 | **/ 39 | 40 | #ifndef HEADER_SIMPLE_OPENGL_IMAGE_LIBRARY 41 | #define HEADER_SIMPLE_OPENGL_IMAGE_LIBRARY 42 | 43 | #ifdef __cplusplus 44 | extern "C" { 45 | #endif 46 | 47 | /** 48 | The format of images that may be loaded (force_channels). 49 | SOIL_LOAD_AUTO leaves the image in whatever format it was found. 50 | SOIL_LOAD_L forces the image to load as Luminous (greyscale) 51 | SOIL_LOAD_LA forces the image to load as Luminous with Alpha 52 | SOIL_LOAD_RGB forces the image to load as Red Green Blue 53 | SOIL_LOAD_RGBA forces the image to load as Red Green Blue Alpha 54 | **/ 55 | enum 56 | { 57 | SOIL_LOAD_AUTO = 0, 58 | SOIL_LOAD_L = 1, 59 | SOIL_LOAD_LA = 2, 60 | SOIL_LOAD_RGB = 3, 61 | SOIL_LOAD_RGBA = 4 62 | }; 63 | 64 | /** 65 | Passed in as reuse_texture_ID, will cause SOIL to 66 | register a new texture ID using glGenTextures(). 67 | If the value passed into reuse_texture_ID > 0 then 68 | SOIL will just re-use that texture ID (great for 69 | reloading image assets in-game!) 70 | **/ 71 | enum 72 | { 73 | SOIL_CREATE_NEW_ID = 0 74 | }; 75 | 76 | /** 77 | flags you can pass into SOIL_load_OGL_texture() 78 | and SOIL_create_OGL_texture(). 79 | (note that if SOIL_FLAG_DDS_LOAD_DIRECT is used 80 | the rest of the flags with the exception of 81 | SOIL_FLAG_TEXTURE_REPEATS will be ignored while 82 | loading already-compressed DDS files.) 83 | 84 | SOIL_FLAG_POWER_OF_TWO: force the image to be POT 85 | SOIL_FLAG_MIPMAPS: generate mipmaps for the texture 86 | SOIL_FLAG_TEXTURE_REPEATS: otherwise will clamp 87 | SOIL_FLAG_MULTIPLY_ALPHA: for using (GL_ONE,GL_ONE_MINUS_SRC_ALPHA) blending 88 | SOIL_FLAG_INVERT_Y: flip the image vertically 89 | SOIL_FLAG_COMPRESS_TO_DXT: if the card can display them, will convert RGB to DXT1, RGBA to DXT5 90 | SOIL_FLAG_DDS_LOAD_DIRECT: will load DDS files directly without _ANY_ additional processing 91 | SOIL_FLAG_NTSC_SAFE_RGB: clamps RGB components to the range [16,235] 92 | SOIL_FLAG_CoCg_Y: Google YCoCg; RGB=>CoYCg, RGBA=>CoCgAY 93 | SOIL_FLAG_TEXTURE_RECTANGE: uses ARB_texture_rectangle ; pixel indexed & no repeat or MIPmaps or cubemaps 94 | **/ 95 | enum 96 | { 97 | SOIL_FLAG_POWER_OF_TWO = 1, 98 | SOIL_FLAG_MIPMAPS = 2, 99 | SOIL_FLAG_TEXTURE_REPEATS = 4, 100 | SOIL_FLAG_MULTIPLY_ALPHA = 8, 101 | SOIL_FLAG_INVERT_Y = 16, 102 | SOIL_FLAG_COMPRESS_TO_DXT = 32, 103 | SOIL_FLAG_DDS_LOAD_DIRECT = 64, 104 | SOIL_FLAG_NTSC_SAFE_RGB = 128, 105 | SOIL_FLAG_CoCg_Y = 256, 106 | SOIL_FLAG_TEXTURE_RECTANGLE = 512 107 | }; 108 | 109 | /** 110 | The types of images that may be saved. 111 | (TGA supports uncompressed RGB / RGBA) 112 | (BMP supports uncompressed RGB) 113 | (DDS supports DXT1 and DXT5) 114 | **/ 115 | enum 116 | { 117 | SOIL_SAVE_TYPE_TGA = 0, 118 | SOIL_SAVE_TYPE_BMP = 1, 119 | SOIL_SAVE_TYPE_DDS = 2 120 | }; 121 | 122 | /** 123 | Defines the order of faces in a DDS cubemap. 124 | I recommend that you use the same order in single 125 | image cubemap files, so they will be interchangeable 126 | with DDS cubemaps when using SOIL. 127 | **/ 128 | #define SOIL_DDS_CUBEMAP_FACE_ORDER "EWUDNS" 129 | 130 | /** 131 | The types of internal fake HDR representations 132 | 133 | SOIL_HDR_RGBE: RGB * pow( 2.0, A - 128.0 ) 134 | SOIL_HDR_RGBdivA: RGB / A 135 | SOIL_HDR_RGBdivA2: RGB / (A*A) 136 | **/ 137 | enum 138 | { 139 | SOIL_HDR_RGBE = 0, 140 | SOIL_HDR_RGBdivA = 1, 141 | SOIL_HDR_RGBdivA2 = 2 142 | }; 143 | 144 | /** 145 | Loads an image from disk into an OpenGL texture. 146 | \param filename the name of the file to upload as a texture 147 | \param force_channels 0-image format, 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA 148 | \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) 149 | \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT 150 | \return 0-failed, otherwise returns the OpenGL texture handle 151 | **/ 152 | unsigned int 153 | SOIL_load_OGL_texture 154 | ( 155 | const char *filename, 156 | int force_channels, 157 | unsigned int reuse_texture_ID, 158 | unsigned int flags 159 | ); 160 | 161 | /** 162 | Loads 6 images from disk into an OpenGL cubemap texture. 163 | \param x_pos_file the name of the file to upload as the +x cube face 164 | \param x_neg_file the name of the file to upload as the -x cube face 165 | \param y_pos_file the name of the file to upload as the +y cube face 166 | \param y_neg_file the name of the file to upload as the -y cube face 167 | \param z_pos_file the name of the file to upload as the +z cube face 168 | \param z_neg_file the name of the file to upload as the -z cube face 169 | \param force_channels 0-image format, 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA 170 | \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) 171 | \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT 172 | \return 0-failed, otherwise returns the OpenGL texture handle 173 | **/ 174 | unsigned int 175 | SOIL_load_OGL_cubemap 176 | ( 177 | const char *x_pos_file, 178 | const char *x_neg_file, 179 | const char *y_pos_file, 180 | const char *y_neg_file, 181 | const char *z_pos_file, 182 | const char *z_neg_file, 183 | int force_channels, 184 | unsigned int reuse_texture_ID, 185 | unsigned int flags 186 | ); 187 | 188 | /** 189 | Loads 1 image from disk and splits it into an OpenGL cubemap texture. 190 | \param filename the name of the file to upload as a texture 191 | \param face_order the order of the faces in the file, any combination of NSWEUD, for North, South, Up, etc. 192 | \param force_channels 0-image format, 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA 193 | \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) 194 | \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT 195 | \return 0-failed, otherwise returns the OpenGL texture handle 196 | **/ 197 | unsigned int 198 | SOIL_load_OGL_single_cubemap 199 | ( 200 | const char *filename, 201 | const char face_order[6], 202 | int force_channels, 203 | unsigned int reuse_texture_ID, 204 | unsigned int flags 205 | ); 206 | 207 | /** 208 | Loads an HDR image from disk into an OpenGL texture. 209 | \param filename the name of the file to upload as a texture 210 | \param fake_HDR_format SOIL_HDR_RGBE, SOIL_HDR_RGBdivA, SOIL_HDR_RGBdivA2 211 | \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) 212 | \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT 213 | \return 0-failed, otherwise returns the OpenGL texture handle 214 | **/ 215 | unsigned int 216 | SOIL_load_OGL_HDR_texture 217 | ( 218 | const char *filename, 219 | int fake_HDR_format, 220 | int rescale_to_max, 221 | unsigned int reuse_texture_ID, 222 | unsigned int flags 223 | ); 224 | 225 | /** 226 | Loads an image from RAM into an OpenGL texture. 227 | \param buffer the image data in RAM just as if it were still in a file 228 | \param buffer_length the size of the buffer in bytes 229 | \param force_channels 0-image format, 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA 230 | \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) 231 | \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT 232 | \return 0-failed, otherwise returns the OpenGL texture handle 233 | **/ 234 | unsigned int 235 | SOIL_load_OGL_texture_from_memory 236 | ( 237 | const unsigned char *const buffer, 238 | int buffer_length, 239 | int force_channels, 240 | unsigned int reuse_texture_ID, 241 | unsigned int flags 242 | ); 243 | 244 | /** 245 | Loads 6 images from memory into an OpenGL cubemap texture. 246 | \param x_pos_buffer the image data in RAM to upload as the +x cube face 247 | \param x_pos_buffer_length the size of the above buffer 248 | \param x_neg_buffer the image data in RAM to upload as the +x cube face 249 | \param x_neg_buffer_length the size of the above buffer 250 | \param y_pos_buffer the image data in RAM to upload as the +x cube face 251 | \param y_pos_buffer_length the size of the above buffer 252 | \param y_neg_buffer the image data in RAM to upload as the +x cube face 253 | \param y_neg_buffer_length the size of the above buffer 254 | \param z_pos_buffer the image data in RAM to upload as the +x cube face 255 | \param z_pos_buffer_length the size of the above buffer 256 | \param z_neg_buffer the image data in RAM to upload as the +x cube face 257 | \param z_neg_buffer_length the size of the above buffer 258 | \param force_channels 0-image format, 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA 259 | \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) 260 | \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT 261 | \return 0-failed, otherwise returns the OpenGL texture handle 262 | **/ 263 | unsigned int 264 | SOIL_load_OGL_cubemap_from_memory 265 | ( 266 | const unsigned char *const x_pos_buffer, 267 | int x_pos_buffer_length, 268 | const unsigned char *const x_neg_buffer, 269 | int x_neg_buffer_length, 270 | const unsigned char *const y_pos_buffer, 271 | int y_pos_buffer_length, 272 | const unsigned char *const y_neg_buffer, 273 | int y_neg_buffer_length, 274 | const unsigned char *const z_pos_buffer, 275 | int z_pos_buffer_length, 276 | const unsigned char *const z_neg_buffer, 277 | int z_neg_buffer_length, 278 | int force_channels, 279 | unsigned int reuse_texture_ID, 280 | unsigned int flags 281 | ); 282 | 283 | /** 284 | Loads 1 image from RAM and splits it into an OpenGL cubemap texture. 285 | \param buffer the image data in RAM just as if it were still in a file 286 | \param buffer_length the size of the buffer in bytes 287 | \param face_order the order of the faces in the file, any combination of NSWEUD, for North, South, Up, etc. 288 | \param force_channels 0-image format, 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA 289 | \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) 290 | \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT 291 | \return 0-failed, otherwise returns the OpenGL texture handle 292 | **/ 293 | unsigned int 294 | SOIL_load_OGL_single_cubemap_from_memory 295 | ( 296 | const unsigned char *const buffer, 297 | int buffer_length, 298 | const char face_order[6], 299 | int force_channels, 300 | unsigned int reuse_texture_ID, 301 | unsigned int flags 302 | ); 303 | 304 | /** 305 | Creates a 2D OpenGL texture from raw image data. Note that the raw data is 306 | _NOT_ freed after the upload (so the user can load various versions). 307 | \param data the raw data to be uploaded as an OpenGL texture 308 | \param width the width of the image in pixels 309 | \param height the height of the image in pixels 310 | \param channels the number of channels: 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA 311 | \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) 312 | \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT 313 | \return 0-failed, otherwise returns the OpenGL texture handle 314 | **/ 315 | unsigned int 316 | SOIL_create_OGL_texture 317 | ( 318 | const unsigned char *const data, 319 | int width, int height, int channels, 320 | unsigned int reuse_texture_ID, 321 | unsigned int flags 322 | ); 323 | 324 | /** 325 | Creates an OpenGL cubemap texture by splitting up 1 image into 6 parts. 326 | \param data the raw data to be uploaded as an OpenGL texture 327 | \param width the width of the image in pixels 328 | \param height the height of the image in pixels 329 | \param channels the number of channels: 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA 330 | \param face_order the order of the faces in the file, and combination of NSWEUD, for North, South, Up, etc. 331 | \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) 332 | \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT 333 | \return 0-failed, otherwise returns the OpenGL texture handle 334 | **/ 335 | unsigned int 336 | SOIL_create_OGL_single_cubemap 337 | ( 338 | const unsigned char *const data, 339 | int width, int height, int channels, 340 | const char face_order[6], 341 | unsigned int reuse_texture_ID, 342 | unsigned int flags 343 | ); 344 | 345 | /** 346 | Captures the OpenGL window (RGB) and saves it to disk 347 | \return 0 if it failed, otherwise returns 1 348 | **/ 349 | int 350 | SOIL_save_screenshot 351 | ( 352 | const char *filename, 353 | int image_type, 354 | int x, int y, 355 | int width, int height 356 | ); 357 | 358 | /** 359 | Loads an image from disk into an array of unsigned chars. 360 | Note that *channels return the original channel count of the 361 | image. If force_channels was other than SOIL_LOAD_AUTO, 362 | the resulting image has force_channels, but *channels may be 363 | different (if the original image had a different channel 364 | count). 365 | \return 0 if failed, otherwise returns 1 366 | **/ 367 | unsigned char* 368 | SOIL_load_image 369 | ( 370 | const char *filename, 371 | int *width, int *height, int *channels, 372 | int force_channels 373 | ); 374 | 375 | /** 376 | Loads an image from memory into an array of unsigned chars. 377 | Note that *channels return the original channel count of the 378 | image. If force_channels was other than SOIL_LOAD_AUTO, 379 | the resulting image has force_channels, but *channels may be 380 | different (if the original image had a different channel 381 | count). 382 | \return 0 if failed, otherwise returns 1 383 | **/ 384 | unsigned char* 385 | SOIL_load_image_from_memory 386 | ( 387 | const unsigned char *const buffer, 388 | int buffer_length, 389 | int *width, int *height, int *channels, 390 | int force_channels 391 | ); 392 | 393 | /** 394 | Saves an image from an array of unsigned chars (RGBA) to disk 395 | \return 0 if failed, otherwise returns 1 396 | **/ 397 | int 398 | SOIL_save_image 399 | ( 400 | const char *filename, 401 | int image_type, 402 | int width, int height, int channels, 403 | const unsigned char *const data 404 | ); 405 | 406 | /** 407 | Frees the image data (note, this is just C's "free()"...this function is 408 | present mostly so C++ programmers don't forget to use "free()" and call 409 | "delete []" instead [8^) 410 | **/ 411 | void 412 | SOIL_free_image_data 413 | ( 414 | unsigned char *img_data 415 | ); 416 | 417 | /** 418 | This function resturn a pointer to a string describing the last thing 419 | that happened inside SOIL. It can be used to determine why an image 420 | failed to load. 421 | **/ 422 | const char* 423 | SOIL_last_result 424 | ( 425 | void 426 | ); 427 | 428 | 429 | #ifdef __cplusplus 430 | } 431 | #endif 432 | 433 | #endif /* HEADER_SIMPLE_OPENGL_IMAGE_LIBRARY */ 434 | -------------------------------------------------------------------------------- /src/app/wasm/3d-cube/soil/libSOIL.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boyanio/angular-wasm/843e6d8192b3286c43851e5c782e24b6755fb59d/src/app/wasm/3d-cube/soil/libSOIL.bc -------------------------------------------------------------------------------- /src/app/wasm/bmp-to-ascii/bmp-to-ascii.component.css: -------------------------------------------------------------------------------- 1 | .img-thumbnail { 2 | cursor: pointer; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/wasm/bmp-to-ascii/bmp-to-ascii.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Bitmap to ASCII art converter

4 |

This example allows you to convert simple bitmaps to ASCII art.

5 |
6 | 7 |
8 |
9 |
10 |
11 |
Choose one of the predefined bitmaps
12 |
13 |
14 | Hello 15 |
16 |
17 |
18 |
19 |
20 |
21 |
Upload your own bitmap
22 |
23 |
24 |
25 |
26 | 32 | 33 |
34 |
35 | It supports only very simple BPM images. 36 |
37 |
38 | 39 |
40 |
41 |
42 |
43 |
{{ output }}
44 |
45 |
46 |
47 |
48 |
49 | -------------------------------------------------------------------------------- /src/app/wasm/bmp-to-ascii/bmp-to-ascii.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, NgZone } from "@angular/core"; 2 | import { HttpClient } from "@angular/common/http"; 3 | import { DomSanitizer } from "@angular/platform-browser"; 4 | import { SafeUrl } from "@angular/platform-browser"; 5 | import { EmscriptenWasmComponent } from "../emscripten-wasm.component"; 6 | import { NgForOf, NgIf } from "@angular/common"; 7 | 8 | const getFileName = (filePath: string) => filePath.split("/").reverse()[0]; 9 | 10 | const allowedMimeTypes = ["image/bmp", "image/x-windows-bmp"]; 11 | 12 | @Component({ 13 | templateUrl: "./bmp-to-ascii.component.html", 14 | styleUrls: ["./bmp-to-ascii.component.css"], 15 | imports: [NgIf, NgForOf], 16 | }) 17 | export class WasmBmpToAsciiComponent extends EmscriptenWasmComponent { 18 | output: string; 19 | uploadedFile: SafeUrl; 20 | predefinedImages: string[]; 21 | error: string; 22 | fileUploadAccept: string; 23 | 24 | constructor(private ngZone: NgZone, private domSanitizer: DomSanitizer, private httpClient: HttpClient) { 25 | super("BmpAsciiModule", "bmp-to-ascii.js"); 26 | 27 | this.fileUploadAccept = allowedMimeTypes.join(","); 28 | this.predefinedImages = [ 29 | "assets/img/ascii/hello.bmp", 30 | "assets/img/ascii/angular.bmp", 31 | "assets/img/ascii/heart.bmp", 32 | ]; 33 | 34 | this.moduleDecorator = (mod) => { 35 | mod.printErr = (what: string) => { 36 | this.ngZone.run(() => (this.error = what)); 37 | }; 38 | }; 39 | } 40 | 41 | convertPredefinedBitmap(index: number) { 42 | this.uploadedFile = null; 43 | this.error = null; 44 | 45 | const imageUrl: string = this.predefinedImages[index]; 46 | this.httpClient.get(imageUrl, { responseType: "arraybuffer" }).subscribe((imageBytes) => { 47 | this.convertToAscii(getFileName(imageUrl), new Uint8Array(imageBytes)); 48 | }); 49 | } 50 | 51 | onFileUploaded(files: FileList) { 52 | if (!files.length) { 53 | return; 54 | } 55 | 56 | this.error = null; 57 | 58 | const file = files[0]; 59 | if (allowedMimeTypes.indexOf(file.type) < 0) { 60 | this.error = `Unsupported mime type ${file.type}`; 61 | return; 62 | } 63 | 64 | const fileName = file.name; 65 | 66 | const reader = new FileReader(); 67 | reader.onload = () => { 68 | const inputArray = new Uint8Array(reader.result); 69 | this.convertToAscii(fileName, inputArray); 70 | 71 | this.ngZone.run(() => { 72 | this.uploadedFile = this.domSanitizer.bypassSecurityTrustUrl(URL.createObjectURL(new Blob([inputArray]))); 73 | }); 74 | }; 75 | reader.readAsArrayBuffer(file); 76 | } 77 | 78 | private convertToAscii(fileName: string, inputArray: Uint8Array) { 79 | // Create a virtual file name from the selected file 80 | this.createDataFile(fileName, inputArray, true); 81 | const resultCode = this.module.ccall("bmp_to_ascii", "int", ["string"], [fileName]); 82 | this.module.FS_unlink(fileName); 83 | 84 | if (resultCode) { 85 | throw Error("The selected file cannot be converted to ASCII"); 86 | } 87 | 88 | this.ngZone.run(() => { 89 | // Read the contents of the created virtual output file 90 | this.output = this.module.FS_readFile("output.txt", { encoding: "utf8" }); 91 | }); 92 | } 93 | 94 | private createDataFile(fileName: string, inputArray: Uint8Array, canRead?: boolean, canWrite?: boolean) { 95 | try { 96 | this.module.FS_createDataFile("/", fileName, inputArray, canRead, canWrite); 97 | } catch (err) { 98 | if (err.code !== "EEXIST") { 99 | throw err; 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/app/wasm/bmp-to-ascii/bmp-to-ascii.cpp: -------------------------------------------------------------------------------- 1 | // Primitive BMP to ASCII art generator 2 | // Reads source.bmp and outputs art.txt 3 | // Source must be 24-bit .bmp 4 | // http://www.dreamincode.net/code/snippet957.htm 5 | // Modified Sept 2010 Bill Thibault to make standalone, platform-indep. 6 | // only works on 24-bit uncompressed BMP files. some of them anyway. 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace std; 14 | 15 | typedef unsigned short uint16; 16 | typedef unsigned int uint32; 17 | 18 | typedef struct bmp_file_header { 19 | uint16 filetype; // BM 20 | uint32 filesize; // in 32-bit integers 21 | uint32 reserved; // must be 0 22 | uint32 offset; // byte offset to start of data 23 | uint32 bytesInHeader; // 40 24 | uint32 width; // in pixels 25 | uint32 height; // in pixels 26 | uint16 planes; // 1 27 | uint16 bitsPerPixel; // 1,4,8, or 24 28 | uint32 compression; // 0 = none, 1 = 8bit RLE, 2 = 4 bit RLE 29 | uint32 size; // of image, in bytes 30 | uint32 horizRes; // in pixels/m 31 | uint32 vertRes; // " 32 | uint32 indicesUsed; // # color indices used by the bitmap 33 | uint32 indicesImportant; // # important indices (0=all) 34 | } BMPFileHeader; 35 | 36 | #define MAX_SHADES 10 37 | #define DEBUG 1 38 | 39 | uint16 extractShort (ifstream &f) { 40 | char buf[2]; 41 | f.read ( buf, 2 ); 42 | uint16 value = buf[0] | (buf[1] << 8); 43 | return value; 44 | } 45 | 46 | uint32 extractInt (ifstream &f) { 47 | char buf[4]; 48 | f.read ( buf, 4 ); 49 | uint32 value = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); 50 | return value; 51 | } 52 | 53 | void readHeader (ifstream& f, BMPFileHeader& header) { 54 | header.filetype = extractShort ( f ); 55 | header.filesize = extractInt ( f ); 56 | header.reserved = extractInt ( f ); 57 | header.offset = extractInt ( f ); 58 | header.bytesInHeader = extractInt ( f ); 59 | header.width = extractInt ( f ); 60 | header.height = extractInt ( f ); 61 | header.planes = extractShort ( f ); 62 | header.bitsPerPixel = extractShort ( f ); 63 | header.compression = extractInt ( f ); 64 | header.size = extractInt ( f ); 65 | header.horizRes = extractInt ( f ); 66 | header.vertRes = extractInt ( f ); 67 | header.indicesUsed = extractInt ( f ); 68 | header.indicesImportant = extractInt ( f ); 69 | } 70 | 71 | extern "C" { 72 | EMSCRIPTEN_KEEPALIVE 73 | int bmp_to_ascii(char* bitmap_file) { 74 | int width, height; 75 | ofstream fout; 76 | unsigned char *image; 77 | char shades[MAX_SHADES] = {'#','$','O','=','+','|','-','^','.',' '}; 78 | int average_color = 0; 79 | 80 | ifstream bmpfile; 81 | BMPFileHeader header; 82 | 83 | // Open the image file 84 | std::string filename = std::string(bitmap_file); 85 | bmpfile.open ( filename.c_str(), ios::in | ios::binary ); 86 | if ( ! bmpfile ) { 87 | cout << "cannot open " << filename << endl; 88 | return 1; 89 | } 90 | 91 | // Read header 92 | readHeader ( bmpfile, header ); 93 | 94 | // Read image 95 | width = int(header.width); 96 | if ( width < 0 ) 97 | width *= -1; 98 | height = int(header.height); 99 | if ( height < 0 ) 100 | height *= -1; 101 | 102 | int rowsize = width * 3; 103 | 104 | image = new unsigned char [ rowsize * height ]; 105 | 106 | bmpfile.seekg ( header.offset, ios::beg ); 107 | bmpfile.read ( (char *)image, 3*width*height ); 108 | bmpfile.close(); 109 | 110 | fout.open("output.txt"); 111 | 112 | // write to the standard output 113 | for (int y = height-1; y >= 0; y--) { 114 | 115 | for (int x = 0; x < width; x++) { 116 | 117 | // Get the average color 118 | average_color = ( image[x*3 + y*rowsize] + 119 | image[x*3 + 1 + y*rowsize] + 120 | image[x*3 + 2 + y*rowsize] ) / 3; 121 | 122 | // Convert to a shade 123 | average_color /= (256/MAX_SHADES); 124 | if(average_color >= MAX_SHADES) 125 | average_color = MAX_SHADES-1; 126 | 127 | // Output 128 | fout << shades[average_color]; 129 | } 130 | 131 | fout << endl; 132 | } 133 | 134 | fout.close(); 135 | return 0; 136 | } 137 | } -------------------------------------------------------------------------------- /src/app/wasm/bmp-to-ascii/build-cmd.js: -------------------------------------------------------------------------------- 1 | exports.cmd = 2 | "docker run --rm -v $(pwd):/src emscripten/emsdk em++ -Os src/app/wasm/bmp-to-ascii/bmp-to-ascii.cpp -o src/assets/wasm/bmp-to-ascii.js --use-preload-plugins -s EXTRA_EXPORTED_RUNTIME_METHODS=\"['ccall','FS_readFile']\" -s MODULARIZE=1 -s EXPORT_NAME=\"BmpAsciiModule\""; 3 | -------------------------------------------------------------------------------- /src/app/wasm/console-logger/build-cmd.js: -------------------------------------------------------------------------------- 1 | exports.cmd = 2 | 'docker run --rm -v $(pwd):/src emscripten/emsdk emcc -Os src/app/wasm/console-logger/console-logger.c -o src/assets/wasm/console-logger.js -s MODULARIZE=1 -s EXPORT_NAME="ConsoleLoggerModule"'; 3 | -------------------------------------------------------------------------------- /src/app/wasm/console-logger/console-logger.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define ARR_SIZE(arr) ( sizeof(arr) / sizeof(arr[0]) ) 6 | 7 | char *str[5]; 8 | 9 | void init_greetings() { 10 | str[0] = "You look nice today"; 11 | str[1] = "Affirmative"; 12 | str[2] = "Rock 'n roll"; 13 | str[3] = "OutOfMemoryException!!! Just kiddin'"; 14 | str[4] = "You need a beer"; 15 | } 16 | 17 | EM_BOOL mouse_callback(int eventType, const EmscriptenMouseEvent *e, void *userData) 18 | { 19 | printf("%s. You clicked at (%ld, %ld)\n", str[(e->clientX + e->clientY) % ARR_SIZE(str)], e->clientX, e->clientY); 20 | return 0; 21 | } 22 | 23 | int main(int argc, char *argv[]) 24 | { 25 | init_greetings(); 26 | 27 | printf("Hello, Angular\n"); 28 | 29 | emscripten_set_click_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, 0, mouse_callback); 30 | return 0; 31 | } -------------------------------------------------------------------------------- /src/app/wasm/console-logger/console-logger.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Console logger

4 |

5 | Just start clicking randomly on the page. This example binds to window click directly in the C code by using 6 | Emscripten's library. The C code uses printf, which I have overloaded to add items to the list instead of 7 | printing them to the console (default behavior). 8 |

9 |
10 |
    11 |
  • {{ logItem }}
  • 12 |
13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /src/app/wasm/console-logger/console-logger.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, NgZone } from "@angular/core"; 2 | import { EmscriptenWasmComponent } from "../emscripten-wasm.component"; 3 | import { NgForOf } from "@angular/common"; 4 | 5 | @Component({ 6 | templateUrl: "./console-logger.component.html", 7 | imports: [NgForOf], 8 | }) 9 | export class WasmConsoleLoggerComponent extends EmscriptenWasmComponent { 10 | logItems: string[] = []; 11 | 12 | constructor(ngZone: NgZone) { 13 | super("ConsoleLoggerModule", "console-logger.js"); 14 | 15 | this.moduleDecorator = (mod) => { 16 | mod.print = (what: string) => { 17 | ngZone.run(() => this.logItems.push(what)); 18 | }; 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/app/wasm/emscripten-wasm.component.ts: -------------------------------------------------------------------------------- 1 | import { AfterViewInit, Directive } from "@angular/core"; 2 | import { loadScript } from "./tools"; 3 | import { environment } from "../../environments/environment"; 4 | import wasmCacheBusting from "../../wasm-cache-busting.json"; 5 | 6 | type EmscriptenModuleDecorator = (module: M) => void; 7 | 8 | const noopModuleDecorator = (mod: EmscriptenModule) => mod; 9 | 10 | @Directive() 11 | export abstract class EmscriptenWasmComponent implements AfterViewInit { 12 | private resolvedModule: M; 13 | protected moduleDecorator: EmscriptenModuleDecorator; 14 | 15 | protected constructor(private moduleExportName: string, private wasmJavaScriptLoader: string) {} 16 | 17 | get module(): M { 18 | return this.resolvedModule; 19 | } 20 | 21 | ngAfterViewInit(): void { 22 | this.resolveModule(); 23 | } 24 | 25 | protected resolveModule(): void { 26 | const jsVersion = wasmCacheBusting[this.wasmJavaScriptLoader] 27 | ? `?v=${wasmCacheBusting[this.wasmJavaScriptLoader]}` 28 | : ""; 29 | loadScript(this.moduleExportName, `${environment.wasmAssetsPath}/${this.wasmJavaScriptLoader}${jsVersion}`) 30 | .then(() => { 31 | const module = { 32 | locateFile: (file: string) => { 33 | const fileVersion = wasmCacheBusting[file] ? `?v=${wasmCacheBusting[file]}` : ""; 34 | return `${environment.wasmAssetsPath}/${file}${fileVersion}`; 35 | }, 36 | }; 37 | const moduleDecorator: EmscriptenModuleDecorator = this.moduleDecorator || noopModuleDecorator; 38 | moduleDecorator(module); 39 | 40 | return window[this.moduleExportName](module); 41 | }) 42 | .then((mod) => { 43 | this.resolvedModule = mod; 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/app/wasm/fibonacci/benchmark.ts: -------------------------------------------------------------------------------- 1 | import { Observable, range, defer, of, from } from "rxjs"; 2 | import { map, concatAll, delay, reduce } from "rxjs/operators"; 3 | 4 | export type FibonacciFunction = (num: number) => number; 5 | 6 | export interface BenchmarkSuite { 7 | name: string; 8 | fibonacciLoop: FibonacciFunction; 9 | fibonacciRec: FibonacciFunction; 10 | fibonacciMemo: FibonacciFunction; 11 | } 12 | 13 | export interface BenchmarkResult { 14 | name: string; 15 | fibonacciLoop: number; 16 | fibonacciRec: number; 17 | fibonacciMemo: number; 18 | } 19 | 20 | const warmupSuite = (suite: BenchmarkSuite) => { 21 | suite.fibonacciLoop(1); 22 | suite.fibonacciRec(1); 23 | suite.fibonacciMemo(1); 24 | }; 25 | 26 | function runAndMeasure(num: number, runs: number, func: (num: number) => number): Observable { 27 | return range(1, runs).pipe( 28 | map(() => 29 | defer(() => { 30 | const startTime = performance.now(); 31 | func(num); 32 | const endTime = performance.now(); 33 | let diff = endTime - startTime; 34 | if (diff === 0) { 35 | // Add some minimal difference, even if they are equal 36 | diff = 0.000000000001; 37 | } 38 | return of(diff); 39 | }) 40 | ), 41 | concatAll(), 42 | delay(300), 43 | reduce((acc, val) => acc + val / runs, 0) 44 | ); 45 | } 46 | 47 | export function runBenchmark(num: number, runs: number, suites: BenchmarkSuite[]): Observable { 48 | for (const suite of suites) { 49 | warmupSuite(suite); 50 | } 51 | 52 | return from(suites).pipe( 53 | map((suite) => 54 | from(Object.keys(suite).filter((key) => key !== "name")).pipe( 55 | // Create an array of result pairs [{ func: diff }] 56 | map((funcName) => runAndMeasure(num, runs, suite[funcName]).pipe(map((avg) => ({ [funcName]: avg })))), 57 | concatAll(), 58 | 59 | // Convert the array into object { func1: diff1, func2: diff2, ... } 60 | reduce<{ [x: string]: number }, BenchmarkResult>((acc, val) => Object.assign(acc, val), { 61 | name: suite.name, 62 | }) 63 | ) 64 | ), 65 | concatAll(), 66 | reduce((acc, val) => [...acc, val], []) 67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /src/app/wasm/fibonacci/build-cmd.js: -------------------------------------------------------------------------------- 1 | exports.cmd = 2 | "docker run --rm -v $(pwd):/src emscripten/emsdk emcc -Os src/app/wasm/fibonacci/fibonacci.c -o src/assets/wasm/fibonacci.wasm --no-entry"; 3 | -------------------------------------------------------------------------------- /src/app/wasm/fibonacci/fibonacci.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | EMSCRIPTEN_KEEPALIVE 4 | int fibonacciLoop(int num) { 5 | int a = 1; 6 | int b = 1; 7 | 8 | while (num-- > 1) { 9 | int t = a; 10 | a = b; 11 | b += t; 12 | } 13 | return b; 14 | } 15 | 16 | EMSCRIPTEN_KEEPALIVE 17 | int fibonacciRec(int num) { 18 | if (num <= 1) 19 | return 1; 20 | 21 | return fibonacciRec(num - 1) + fibonacciRec(num - 2); 22 | } 23 | 24 | int memo[10000]; 25 | 26 | EMSCRIPTEN_KEEPALIVE 27 | int fibonacciMemo(int num) { 28 | if (memo[num] != -1) 29 | return memo[num]; 30 | 31 | if (num == 1 || num == 2) { 32 | return 1; 33 | } else { 34 | return memo[num] = fibonacciMemo(num - 1) + fibonacciMemo(num - 2); 35 | } 36 | } -------------------------------------------------------------------------------- /src/app/wasm/fibonacci/fibonacci.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Fibonacci battlefield

4 |

5 | This example shows the "raw" communication between JavaScript and WebAssembly without using Emscripten's glue 6 | code. Inspired by 7 | devlucky, the example demonstrates the performance difference between JavaScript and WebAssembly when calculating 13 | Fibonacci series using three different implementations: loop, recursion, memoization. 14 |

15 | 16 |
17 |
18 |
19 |
20 |
21 | 22 | 32 | The number for which you want to calculate the fibonacci series (min. 1) 35 |
36 |
37 |
38 |
39 | 40 | 50 | How many times to run the calculations before taking an average 53 |
54 |
55 |
56 | 57 | 58 |
59 |
60 |
61 | 62 |
63 |
64 |

Results

65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 85 |
 LoopRecursiveMemoization
{{ result.name }} 81 | {{ result[func] | humanizeTime }} {{ fastDiff(result, func) }} 82 |
86 |
87 |
88 |
89 |
90 |
91 | -------------------------------------------------------------------------------- /src/app/wasm/fibonacci/fibonacci.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy } from "@angular/core"; 2 | import { HttpClient } from "@angular/common/http"; 3 | import { Subscription } from "rxjs"; 4 | import { mergeMap } from "rxjs/operators"; 5 | import { environment } from "../../../environments/environment"; 6 | import { fibonacciLoop, fibonacciMemo, fibonacciRec } from "./fibonacci"; 7 | import { BenchmarkSuite, BenchmarkResult, runBenchmark, FibonacciFunction } from "./benchmark"; 8 | import { HumanizeTimePipe } from "../humanize-time-pipe"; 9 | import { LaddaModule } from "angular2-ladda"; 10 | import { FormsModule } from "@angular/forms"; 11 | import { NgForOf, NgIf } from "@angular/common"; 12 | 13 | const jsSuite: BenchmarkSuite = { 14 | name: "JavaScript", 15 | fibonacciLoop, 16 | fibonacciRec, 17 | fibonacciMemo, 18 | }; 19 | 20 | @Component({ 21 | templateUrl: "./fibonacci.component.html", 22 | imports: [HumanizeTimePipe, LaddaModule, FormsModule, NgIf, NgForOf], 23 | }) 24 | export class WasmFibonacciComponent implements OnInit, OnDestroy { 25 | loaded: boolean; 26 | number: number; 27 | runs: number; 28 | isCalculating: boolean; 29 | results: BenchmarkResult[]; 30 | private runSubscription: Subscription; 31 | private wasmSuite: BenchmarkSuite; 32 | 33 | constructor(private http: HttpClient) { 34 | this.number = 25; 35 | this.runs = 10; 36 | } 37 | 38 | ngOnInit() { 39 | // Load "pure" WebAssembly, i.e. without any Emscripten API needed 40 | // to work with it 41 | this.instantiateWasm(`${environment.wasmAssetsPath}/fibonacci.wasm`, {}).then((result) => { 42 | const wasmInstance = result.instance; 43 | 44 | this.wasmSuite = { 45 | name: "WebAssembly", 46 | fibonacciLoop: wasmInstance.exports.fibonacciLoop as FibonacciFunction, 47 | fibonacciRec: wasmInstance.exports.fibonacciRec as FibonacciFunction, 48 | fibonacciMemo: wasmInstance.exports.fibonacciMemo as FibonacciFunction, 49 | }; 50 | this.loaded = true; 51 | }); 52 | } 53 | 54 | ngOnDestroy(): void { 55 | this.unsubscribeRun(); 56 | } 57 | 58 | start() { 59 | if (this.number < 1 || this.runs < 1) { 60 | return; 61 | } 62 | 63 | this.results = null; 64 | this.isCalculating = true; 65 | 66 | this.unsubscribeRun(); 67 | 68 | this.runSubscription = runBenchmark(this.number, this.runs, [jsSuite, this.wasmSuite]).subscribe((results) => { 69 | this.isCalculating = false; 70 | this.results = results; 71 | }); 72 | } 73 | 74 | cellClass(result: BenchmarkResult, func: string) { 75 | return this.isFastest(result, func) ? "table-success" : ""; 76 | } 77 | 78 | fastDiff(result: BenchmarkResult, func: string) { 79 | if (!this.isFastest(result, func)) { 80 | return ""; 81 | } 82 | 83 | const fastest = result[func]; 84 | const slowest = this.results.map((r) => r[func]).reduce((prev, val) => Math.max(prev, val), fastest); 85 | const diff = slowest / fastest; 86 | return ` (${diff > 1 && diff < 2 ? diff.toFixed(2) : Math.round(diff)}x)`; 87 | } 88 | 89 | private isFastest(result: BenchmarkResult, func: string) { 90 | return this.results.every((r) => r[func] >= result[func]); 91 | } 92 | 93 | private instantiateWasm( 94 | url: string, 95 | imports?: WebAssembly.Imports 96 | ): Promise { 97 | return this.http 98 | .get(url, { responseType: "arraybuffer" }) 99 | .pipe(mergeMap((bytes) => WebAssembly.instantiate(bytes, imports))) 100 | .toPromise(); 101 | } 102 | 103 | private unsubscribeRun() { 104 | if (this.runSubscription) { 105 | this.runSubscription.unsubscribe(); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/app/wasm/fibonacci/fibonacci.ts: -------------------------------------------------------------------------------- 1 | export const fibonacciLoop = (num: number) => { 2 | let a = 1; 3 | let b = 0; 4 | let temp: number; 5 | 6 | while (num >= 0) { 7 | temp = a; 8 | a = a + b; 9 | b = temp; 10 | num--; 11 | } 12 | 13 | return b; 14 | }; 15 | 16 | export const fibonacciRec = (num: number) => { 17 | if (num <= 1) { 18 | return 1; 19 | } 20 | 21 | return fibonacciRec(num - 1) + fibonacciRec(num - 2); 22 | }; 23 | 24 | export const fibonacciMemo = (num: number, memo?: Record) => { 25 | memo = memo || {}; 26 | 27 | if (memo[num]) { 28 | return memo[num]; 29 | } 30 | 31 | if (num <= 1) { 32 | return 1; 33 | } 34 | 35 | return (memo[num] = fibonacciMemo(num - 1, memo) + fibonacciMemo(num - 2, memo)); 36 | }; 37 | -------------------------------------------------------------------------------- /src/app/wasm/humanize-time-pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from "@angular/core"; 2 | 3 | const zeroRegex = new RegExp("^0[\\.\\,]0+$"); 4 | 5 | const numberToFixed = (num: number) => { 6 | const fractionDigits = 5; 7 | let res = num.toFixed(fractionDigits); 8 | if (zeroRegex.test(res)) { 9 | // The number is 0.00000 10 | res = `0.${"0".repeat(fractionDigits - 1)}1`; 11 | } 12 | return res; 13 | }; 14 | 15 | /* 16 | * Converts ms to human-readable time string 17 | * Usage: 18 | * value | humanizeTime 19 | * Example: 20 | * {{ 2001 | humanizeTime }} 21 | * formats to: 2s 1ms 22 | * 23 | * {{ 1.234 | humanizeTime }} 24 | * formats to: 1.234ms 25 | */ 26 | @Pipe({ 27 | name: "humanizeTime", 28 | }) 29 | export class HumanizeTimePipe implements PipeTransform { 30 | transform(value: number) { 31 | const milliseconds = value % 1000; 32 | const timeTakenInSec = (value - milliseconds) / 1000; 33 | const hours = Math.floor(timeTakenInSec / 3600); 34 | const minutes = Math.floor((timeTakenInSec - hours * 3600) / 60); 35 | const seconds = timeTakenInSec - hours * 3600 - minutes * 60; 36 | 37 | let str = ""; 38 | if (hours > 0) { 39 | str += `${hours}h `; 40 | } 41 | if (minutes > 0) { 42 | str += `${minutes}m `; 43 | } 44 | if (seconds > 0) { 45 | str += `${seconds}s `; 46 | } 47 | if (milliseconds > 0) { 48 | str += numberToFixed(milliseconds) + "ms"; 49 | } 50 | return str; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/app/wasm/person-record/build-cmd.js: -------------------------------------------------------------------------------- 1 | exports.cmd = 2 | 'docker run --rm -v $(pwd):/src emscripten/emsdk em++ -Os --bind src/app/wasm/person-record/person-record.cpp -o src/assets/wasm/person-record.js -s MODULARIZE=1 -s EXPORT_NAME="PersonRecordModule"'; 3 | -------------------------------------------------------------------------------- /src/app/wasm/person-record/person-record.component.css: -------------------------------------------------------------------------------- 1 | .person-record .icon img { 2 | width: auto; 3 | height: 32px; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/wasm/person-record/person-record.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Person record

4 |

5 | This example shows how to pass complex data between JavaScript and WebAssembly. It uses Emscripten's 6 | embind 12 | to make a C++ struct available to JavaScript. 13 |

14 |
15 |
16 |
17 |
18 |
19 |
20 | 28 |
29 |
30 |
31 | 41 |
42 | years 43 |
44 |
45 |
46 |
47 |
48 | 58 |
59 | cm 60 |
61 |
62 |
63 |
64 |
65 | 75 |
76 | kg 77 |
78 |
79 |
80 |
81 | 84 |
85 |
86 |
87 |
88 |
89 |
90 | avatar 91 |
92 |
Hello, I am {{ record.personalInfo.name }}
93 |

94 | I have the very unique ID {{ record.id }} generated as a hash of my data. 95 |

96 |
97 |
98 | 99 | age 100 | 101 | {{ record.personalInfo.age }} years 102 |
103 |
104 | 105 | height 106 | 107 | {{ record.body.height }} cm 108 |
109 |
110 | 111 | weight 112 | 113 | {{ record.body.weight }} kg 114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 | -------------------------------------------------------------------------------- /src/app/wasm/person-record/person-record.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { EmscriptenWasmComponent } from "../emscripten-wasm.component"; 3 | import { FormsModule } from "@angular/forms"; 4 | import { NgIf } from "@angular/common"; 5 | 6 | interface PersonalInfo { 7 | name: string; 8 | age: number; 9 | } 10 | 11 | interface Body { 12 | height: number; 13 | weight: number; 14 | } 15 | 16 | interface PersonRecord { 17 | id: number; 18 | avatar: string; 19 | personalInfo: PersonalInfo; 20 | body: Body; 21 | } 22 | 23 | interface MyEmscriptenModule extends EmscriptenModule { 24 | createRecord(personalInfo: PersonalInfo, body: Body): PersonRecord; 25 | } 26 | 27 | @Component({ 28 | templateUrl: "./person-record.component.html", 29 | styleUrls: ["./person-record.component.css"], 30 | imports: [FormsModule, NgIf], 31 | }) 32 | export class WasmPersonRecordComponent extends EmscriptenWasmComponent { 33 | model: { 34 | personalInfo: PersonalInfo; 35 | body: Body; 36 | }; 37 | record?: PersonRecord; 38 | 39 | constructor() { 40 | super("PersonRecordModule", "person-record.js"); 41 | 42 | this.resetModel(); 43 | } 44 | 45 | createRecord() { 46 | this.record = this.module.createRecord(this.model.personalInfo, this.model.body); 47 | this.resetModel(); 48 | } 49 | 50 | private resetModel() { 51 | this.model = { 52 | personalInfo: { 53 | name: null, 54 | age: null, 55 | }, 56 | body: { 57 | height: null, 58 | weight: null, 59 | }, 60 | }; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/app/wasm/person-record/person-record.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace emscripten; 4 | 5 | struct PersonalInfo { 6 | std::string name; 7 | int age; 8 | }; 9 | 10 | struct Body { 11 | int height; 12 | int weight; 13 | }; 14 | 15 | struct PersonRecord { 16 | std::size_t id; 17 | std::string avatar; 18 | PersonalInfo personalInfo; 19 | Body body; 20 | }; 21 | 22 | PersonRecord createRecord(PersonalInfo personalInfo, Body body) { 23 | struct PersonRecord personRecord; 24 | personRecord.id = std::hash{}( 25 | personalInfo.name + 26 | std::to_string(personalInfo.age) + 27 | std::to_string(body.height) + 28 | std::to_string(body.weight)); 29 | 30 | personRecord.avatar = "https://i.pravatar.cc/150?u=" + std::to_string(personRecord.id); 31 | 32 | personRecord.body = body; 33 | personRecord.personalInfo = personalInfo; 34 | 35 | return personRecord; 36 | } 37 | 38 | EMSCRIPTEN_BINDINGS(my_class_example) { 39 | value_object("PersonalInfo") 40 | .field("name", &PersonalInfo::name) 41 | .field("age", &PersonalInfo::age) 42 | ; 43 | 44 | value_object("Body") 45 | .field("height", &Body::height) 46 | .field("weight", &Body::weight) 47 | ; 48 | 49 | value_object("PersonRecord") 50 | .field("id", &PersonRecord::id) 51 | .field("avatar", &PersonRecord::avatar) 52 | .field("body", &PersonRecord::body) 53 | .field("personalInfo", &PersonRecord::personalInfo) 54 | ; 55 | 56 | function("createRecord", &createRecord); 57 | } -------------------------------------------------------------------------------- /src/app/wasm/proof-of-work/build-cmd.js: -------------------------------------------------------------------------------- 1 | exports.cmd = 2 | 'docker run --rm -v $(pwd):/src emscripten/emsdk emcc -Os src/app/wasm/proof-of-work/sha256/sha256.c src/app/wasm/proof-of-work/proof-of-work.c -o src/assets/wasm/proof-of-work.js -s ALLOW_MEMORY_GROWTH=1 -s EXTRA_EXPORTED_RUNTIME_METHODS="[\'ccall\']" -s ASYNCIFY=1 --js-library src/app/wasm/proof-of-work/proof-of-work.emlib.js -s MODULARIZE=1 -s EXPORT_NAME="ProofOfWorkModule"'; 3 | -------------------------------------------------------------------------------- /src/app/wasm/proof-of-work/proof-of-work.c: -------------------------------------------------------------------------------- 1 | // Original code taken from https://github.com/devchild/proof-of-work-in-c 2 | 3 | #include "sha256/sha256.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | extern void proof_of_work_done(int, uint64_t, char *); 12 | 13 | char * format_hash(const BYTE hash[]) { 14 | 15 | char *result; 16 | result = malloc(65*sizeof(char)); 17 | 18 | int idx; 19 | for (idx = 0; idx < 64; idx = idx + 2) { 20 | sprintf(&result[idx], "%02x", hash[idx / 2]); 21 | } 22 | result[64] = '\0'; 23 | return result; 24 | } 25 | 26 | struct Proof { 27 | BYTE * hash; 28 | int64_t nonce; 29 | int32_t nr_leading_zeros; 30 | time_t created_at; 31 | uint64_t iterations; 32 | } Proof; 33 | 34 | uint64_t random_uint64() { 35 | uint64_t r = rand(), q = UINT64_MAX, b = (uint64_t) RAND_MAX + 1; 36 | /* +1 because range of rand() is [0, RAND_MAX] */ 37 | /* We want ceil(log_b(UINT64_MAX + 1)) iterations 38 | = ceil(log_b(UINT64_MAX)) unless log_b(UINT64_MAX) is an integer, which it 39 | is only if b = UINT64_MAX, but the return type of rand() is int. */ 40 | while (q > b) { 41 | r = r * b + (uint64_t) rand(); 42 | q = (q - 1) / b + 1; 43 | } 44 | return r; 45 | } 46 | 47 | int64_t random_int64() { 48 | return random_uint64() + INT64_MIN; 49 | } 50 | 51 | BYTE * compute_sha256(const BYTE data[], size_t data_len) { 52 | BYTE * hash = malloc(sizeof(BYTE) * 32); 53 | SHA256_CTX ctx; 54 | sha256_init(&ctx); 55 | sha256_update(&ctx, data, data_len); 56 | sha256_final(&ctx, hash); 57 | return hash; 58 | } 59 | 60 | int verify(struct Proof * p, const BYTE data[], size_t data_len) { 61 | BYTE buffer[ 62 | data_len 63 | + sizeof(time_t) 64 | + sizeof(int64_t) 65 | ]; 66 | 67 | memcpy(&buffer, data, data_len); 68 | 69 | time_t t = p->created_at; 70 | memcpy(&buffer[data_len], &t, sizeof(t)); 71 | 72 | int64_t nonce = p->nonce; 73 | memcpy(&buffer[data_len + sizeof(t)], &nonce, sizeof(nonce)); 74 | 75 | BYTE* hash = compute_sha256(buffer, sizeof(buffer) / sizeof(BYTE)); 76 | return memcmp(hash, p->hash, data_len); 77 | } 78 | 79 | struct Proof * work(const BYTE data[], size_t data_len, int32_t leadingZeros) { 80 | BYTE buffer[ 81 | data_len 82 | + sizeof(time_t) 83 | + sizeof(int64_t) 84 | ]; 85 | 86 | memcpy(&buffer, data, data_len); 87 | 88 | time_t t = time(NULL); 89 | memcpy(&buffer[data_len], &t, sizeof(t)); 90 | 91 | uint64_t iterations = 1; 92 | 93 | while(1) { 94 | int64_t nonce = random_int64(); 95 | memcpy(&buffer[data_len + sizeof(t)], &nonce, sizeof(nonce)); 96 | 97 | BYTE* hash = compute_sha256(buffer, sizeof(buffer) / sizeof(BYTE)); 98 | int other_than_zero = 1; 99 | for (int i = leadingZeros; i > 0 && other_than_zero; i--) { 100 | other_than_zero = 0x00 == hash[i]; // is zero_bit 101 | } 102 | 103 | if (other_than_zero) { 104 | struct Proof* ret = malloc(sizeof(Proof)); 105 | ret->nonce = nonce; 106 | ret->created_at = t; 107 | ret->nr_leading_zeros = leadingZeros; 108 | ret->hash = hash; 109 | ret->iterations = iterations; 110 | return ret; 111 | } 112 | 113 | // Used to prevent blocking of the main thread 114 | if (iterations % 500 == 0) { 115 | emscripten_sleep(1); 116 | } 117 | 118 | iterations++; 119 | } 120 | } 121 | 122 | EMSCRIPTEN_KEEPALIVE 123 | void do_proof_of_work(unsigned char * str, int32_t leadingZeros) { 124 | struct Proof* p = work(str, sizeof(str) / sizeof(BYTE), leadingZeros); 125 | int result = verify(p, str, sizeof(str) / sizeof(BYTE)); 126 | proof_of_work_done(result, p->iterations, format_hash(p->hash)); 127 | } -------------------------------------------------------------------------------- /src/app/wasm/proof-of-work/proof-of-work.component.css: -------------------------------------------------------------------------------- 1 | .proof-of-work-result { 2 | padding-top: 15px; 3 | } 4 | 5 | h2 { 6 | margin-top: 30px; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/wasm/proof-of-work/proof-of-work.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Proof of work

4 |

5 | A simple 6 | Proof of Work system 7 | (similar to the one used in bitcoin). You need to provide an input string and the required number of leading 8 | zeros. The program tries to find a nonce (by appending random numbers to your input string), whose SHA-256 hash 9 | will have the same number of leading zeros. Beware, the more leading zeros you require, the more time it will 10 | take, the more memory will be needed. You can set the required leading zeros to 3 and see what happens :-) It 11 | could be unstable for higher number of leading zeros. 12 |

13 |
14 | 15 |
16 |
17 | 18 | 25 |
26 |
27 | 28 | 29 |
30 |
31 | 32 | 41 |
42 |
43 |
44 |
45 | 46 | 50 |
51 |
52 |
53 |
54 |

Statistics

55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 |
Leading zerosIterationsTime taken
{{ item.leadingZeros }}{{ item.iterations }}{{ item.timeTaken | humanizeTime }}
71 |
72 |
73 |
74 |
75 |
76 | -------------------------------------------------------------------------------- /src/app/wasm/proof-of-work/proof-of-work.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, NgZone, AfterViewInit, OnDestroy } from "@angular/core"; 2 | import { EmscriptenWasmComponent } from "../emscripten-wasm.component"; 3 | import { HumanizeTimePipe } from "../humanize-time-pipe"; 4 | import { FormsModule } from "@angular/forms"; 5 | import { LaddaModule } from "angular2-ladda"; 6 | import { NgForOf, NgIf } from "@angular/common"; 7 | 8 | export interface Proof { 9 | success: boolean; 10 | iterations: number; 11 | hash: string; 12 | } 13 | 14 | export interface StatisticsItem { 15 | timeTaken: number; 16 | iterations: number; 17 | leadingZeros: number; 18 | } 19 | 20 | const sortStatisticsItems = (a: StatisticsItem, b: StatisticsItem) => a.leadingZeros - b.leadingZeros; 21 | 22 | @Component({ 23 | templateUrl: "./proof-of-work.component.html", 24 | styleUrls: ["./proof-of-work.component.css"], 25 | imports: [HumanizeTimePipe, FormsModule, LaddaModule, NgIf, NgForOf], 26 | }) 27 | export class WasmProofOfWorkComponent extends EmscriptenWasmComponent implements AfterViewInit, OnDestroy { 28 | input: string; 29 | leadingZeros: number; 30 | proof: Proof; 31 | error: string; 32 | isWorking: boolean; 33 | statisticsItems: StatisticsItem[]; 34 | private startTime: number; 35 | 36 | constructor(private ngZone: NgZone) { 37 | super("ProofOfWorkModule", "proof-of-work.js"); 38 | 39 | this.leadingZeros = 1; 40 | this.statisticsItems = []; 41 | 42 | this.moduleDecorator = (mod) => { 43 | mod.printErr = (what: string) => { 44 | if (what && what.startsWith && !what.startsWith("WARNING")) { 45 | ngZone.run(() => (this.error = what)); 46 | } 47 | 48 | console.log(what); 49 | }; 50 | }; 51 | } 52 | 53 | ngAfterViewInit() { 54 | // This will be called from our Emscripten injected library 55 | window["onProofOfWorkDone"] = (result: number, iterations: number, hash: string) => { 56 | this.ngZone.run(() => this.onProofOfWorkDone(result, iterations, hash)); 57 | }; 58 | super.ngAfterViewInit(); 59 | } 60 | 61 | ngOnDestroy() { 62 | delete window["onProofOfWorkDone"]; 63 | } 64 | 65 | get canFindSolution() { 66 | return this.leadingZeros >= 1 && this.leadingZeros <= 9 && !!this.input; 67 | } 68 | 69 | findSolution() { 70 | if (!this.canFindSolution) { 71 | return; 72 | } 73 | 74 | this.proof = null; 75 | this.isWorking = true; 76 | this.startTime = performance.now(); 77 | this.module.ccall("do_proof_of_work", null, ["string", "number"], [this.input, this.leadingZeros], { async: true }); 78 | } 79 | 80 | private onProofOfWorkDone(result: number, iterations: number, hash: string) { 81 | this.isWorking = false; 82 | this.proof = { success: !result, iterations, hash }; 83 | 84 | const endTime = performance.now(); 85 | const timeTaken = endTime - this.startTime; 86 | 87 | this.statisticsItems.push({ leadingZeros: this.leadingZeros, iterations, timeTaken }); 88 | this.statisticsItems.sort(sortStatisticsItems); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/app/wasm/proof-of-work/proof-of-work.emlib.js: -------------------------------------------------------------------------------- 1 | mergeInto(LibraryManager.library, { 2 | proof_of_work_done: function (result, iterations, _, hashPtr) { 3 | window["onProofOfWorkDone"](result, iterations, UTF8ToString(hashPtr)); 4 | }, 5 | }); 6 | -------------------------------------------------------------------------------- /src/app/wasm/proof-of-work/sha256/sha256.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Filename: sha256.c 3 | * Author: Brad Conte (brad AT bradconte.com) 4 | * Copyright: 5 | * Disclaimer: This code is presented "as is" without any guarantees. 6 | * Details: Implementation of the SHA-256 hashing algorithm. 7 | SHA-256 is one of the three algorithms in the SHA2 8 | specification. The others, SHA-384 and SHA-512, are not 9 | offered in this implementation. 10 | Algorithm specification can be found here: 11 | * http://csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf 12 | This implementation uses little endian byte order. 13 | *********************************************************************/ 14 | 15 | /*************************** HEADER FILES ***************************/ 16 | #include 17 | #include 18 | #include "sha256.h" 19 | 20 | /****************************** MACROS ******************************/ 21 | #define ROTLEFT(a,b) (((a) << (b)) | ((a) >> (32-(b)))) 22 | #define ROTRIGHT(a,b) (((a) >> (b)) | ((a) << (32-(b)))) 23 | 24 | #define CH(x,y,z) (((x) & (y)) ^ (~(x) & (z))) 25 | #define MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) 26 | #define EP0(x) (ROTRIGHT(x,2) ^ ROTRIGHT(x,13) ^ ROTRIGHT(x,22)) 27 | #define EP1(x) (ROTRIGHT(x,6) ^ ROTRIGHT(x,11) ^ ROTRIGHT(x,25)) 28 | #define SIG0(x) (ROTRIGHT(x,7) ^ ROTRIGHT(x,18) ^ ((x) >> 3)) 29 | #define SIG1(x) (ROTRIGHT(x,17) ^ ROTRIGHT(x,19) ^ ((x) >> 10)) 30 | 31 | /**************************** VARIABLES *****************************/ 32 | static const WORD k[64] = { 33 | 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5, 34 | 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174, 35 | 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da, 36 | 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967, 37 | 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85, 38 | 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070, 39 | 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3, 40 | 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2 41 | }; 42 | 43 | /*********************** FUNCTION DEFINITIONS ***********************/ 44 | void sha256_transform(SHA256_CTX *ctx, const BYTE data[]) 45 | { 46 | WORD a, b, c, d, e, f, g, h, i, j, t1, t2, m[64]; 47 | 48 | for (i = 0, j = 0; i < 16; ++i, j += 4) 49 | m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]); 50 | for ( ; i < 64; ++i) 51 | m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16]; 52 | 53 | a = ctx->state[0]; 54 | b = ctx->state[1]; 55 | c = ctx->state[2]; 56 | d = ctx->state[3]; 57 | e = ctx->state[4]; 58 | f = ctx->state[5]; 59 | g = ctx->state[6]; 60 | h = ctx->state[7]; 61 | 62 | for (i = 0; i < 64; ++i) { 63 | t1 = h + EP1(e) + CH(e,f,g) + k[i] + m[i]; 64 | t2 = EP0(a) + MAJ(a,b,c); 65 | h = g; 66 | g = f; 67 | f = e; 68 | e = d + t1; 69 | d = c; 70 | c = b; 71 | b = a; 72 | a = t1 + t2; 73 | } 74 | 75 | ctx->state[0] += a; 76 | ctx->state[1] += b; 77 | ctx->state[2] += c; 78 | ctx->state[3] += d; 79 | ctx->state[4] += e; 80 | ctx->state[5] += f; 81 | ctx->state[6] += g; 82 | ctx->state[7] += h; 83 | } 84 | 85 | void sha256_init(SHA256_CTX *ctx) 86 | { 87 | ctx->datalen = 0; 88 | ctx->bitlen = 0; 89 | ctx->state[0] = 0x6a09e667; 90 | ctx->state[1] = 0xbb67ae85; 91 | ctx->state[2] = 0x3c6ef372; 92 | ctx->state[3] = 0xa54ff53a; 93 | ctx->state[4] = 0x510e527f; 94 | ctx->state[5] = 0x9b05688c; 95 | ctx->state[6] = 0x1f83d9ab; 96 | ctx->state[7] = 0x5be0cd19; 97 | } 98 | 99 | void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len) 100 | { 101 | WORD i; 102 | 103 | for (i = 0; i < len; ++i) { 104 | ctx->data[ctx->datalen] = data[i]; 105 | ctx->datalen++; 106 | if (ctx->datalen == 64) { 107 | sha256_transform(ctx, ctx->data); 108 | ctx->bitlen += 512; 109 | ctx->datalen = 0; 110 | } 111 | } 112 | } 113 | 114 | void sha256_final(SHA256_CTX *ctx, BYTE hash[]) 115 | { 116 | WORD i; 117 | 118 | i = ctx->datalen; 119 | 120 | // Pad whatever data is left in the buffer. 121 | if (ctx->datalen < 56) { 122 | ctx->data[i++] = 0x80; 123 | while (i < 56) 124 | ctx->data[i++] = 0x00; 125 | } 126 | else { 127 | ctx->data[i++] = 0x80; 128 | while (i < 64) 129 | ctx->data[i++] = 0x00; 130 | sha256_transform(ctx, ctx->data); 131 | memset(ctx->data, 0, 56); 132 | } 133 | 134 | // Append to the padding the total message's length in bits and transform. 135 | ctx->bitlen += ctx->datalen * 8; 136 | ctx->data[63] = ctx->bitlen; 137 | ctx->data[62] = ctx->bitlen >> 8; 138 | ctx->data[61] = ctx->bitlen >> 16; 139 | ctx->data[60] = ctx->bitlen >> 24; 140 | ctx->data[59] = ctx->bitlen >> 32; 141 | ctx->data[58] = ctx->bitlen >> 40; 142 | ctx->data[57] = ctx->bitlen >> 48; 143 | ctx->data[56] = ctx->bitlen >> 56; 144 | sha256_transform(ctx, ctx->data); 145 | 146 | // Since this implementation uses little endian byte ordering and SHA uses big endian, 147 | // reverse all the bytes when copying the final state to the output hash. 148 | for (i = 0; i < 4; ++i) { 149 | hash[i] = (ctx->state[0] >> (24 - i * 8)) & 0x000000ff; 150 | hash[i + 4] = (ctx->state[1] >> (24 - i * 8)) & 0x000000ff; 151 | hash[i + 8] = (ctx->state[2] >> (24 - i * 8)) & 0x000000ff; 152 | hash[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0x000000ff; 153 | hash[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0x000000ff; 154 | hash[i + 20] = (ctx->state[5] >> (24 - i * 8)) & 0x000000ff; 155 | hash[i + 24] = (ctx->state[6] >> (24 - i * 8)) & 0x000000ff; 156 | hash[i + 28] = (ctx->state[7] >> (24 - i * 8)) & 0x000000ff; 157 | } 158 | } -------------------------------------------------------------------------------- /src/app/wasm/proof-of-work/sha256/sha256.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Filename: sha256.h 3 | * Author: Brad Conte (brad AT bradconte.com) 4 | * Copyright: 5 | * Disclaimer: This code is presented "as is" without any guarantees. 6 | * Details: Defines the API for the corresponding SHA1 implementation. 7 | *********************************************************************/ 8 | 9 | #ifndef SHA256_H 10 | #define SHA256_H 11 | 12 | /*************************** HEADER FILES ***************************/ 13 | #include 14 | 15 | /****************************** MACROS ******************************/ 16 | #define SHA256_BLOCK_SIZE 32 // SHA256 outputs a 32 byte digest 17 | 18 | /**************************** DATA TYPES ****************************/ 19 | typedef unsigned char BYTE; // 8-bit byte 20 | typedef unsigned int WORD; // 32-bit word, change to "long" for 16-bit machines 21 | 22 | typedef struct { 23 | BYTE data[64]; 24 | WORD datalen; 25 | unsigned long long bitlen; 26 | WORD state[8]; 27 | } SHA256_CTX; 28 | 29 | /*********************** FUNCTION DECLARATIONS **********************/ 30 | void sha256_init(SHA256_CTX *ctx); 31 | void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len); 32 | void sha256_final(SHA256_CTX *ctx, BYTE hash[]); 33 | 34 | #endif // SHA256_H -------------------------------------------------------------------------------- /src/app/wasm/text-to-ascii/build-cmd.js: -------------------------------------------------------------------------------- 1 | exports.cmd = 2 | 'docker run --rm -v $(pwd):/src emscripten/emsdk em++ -Os src/app/wasm/text-to-ascii/text-to-ascii.cpp -o src/assets/wasm/text-to-ascii.js --preload-file src/app/wasm/text-to-ascii/text-to-ascii.font.txt -s EXTRA_EXPORTED_RUNTIME_METHODS="[\'ccall\']" -s MODULARIZE=1 -s EXPORT_NAME="TextAsciiModule"'; 3 | -------------------------------------------------------------------------------- /src/app/wasm/text-to-ascii/text-to-ascii.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Text to ASCII art converter

4 |

This example allows you to convert text to ASCII art on the fly.

5 |
6 |
7 |
8 | 9 | 16 |
17 |
18 | 19 | 26 |
27 |
28 | 29 | 36 |
37 |
38 |
39 |         {{ output }}
40 |       
41 |
42 |
43 |
44 | -------------------------------------------------------------------------------- /src/app/wasm/text-to-ascii/text-to-ascii.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, NgZone } from "@angular/core"; 2 | import { EmscriptenWasmComponent } from "../emscripten-wasm.component"; 3 | import { FormsModule } from "@angular/forms"; 4 | 5 | @Component({ 6 | templateUrl: "./text-to-ascii.component.html", 7 | imports: [FormsModule], 8 | }) 9 | export class WasmTextToAsciiComponent extends EmscriptenWasmComponent { 10 | input: string; 11 | foregroundChar: string; 12 | backgroundChar: string; 13 | output: string; 14 | 15 | constructor(ngZone: NgZone) { 16 | super("TextAsciiModule", "text-to-ascii.js"); 17 | 18 | this.output = ""; 19 | this.foregroundChar = "#"; 20 | this.backgroundChar = "."; 21 | 22 | this.moduleDecorator = (mod) => { 23 | mod.print = (what: string) => { 24 | ngZone.run(() => { 25 | this.output += "\n" + what; 26 | }); 27 | }; 28 | }; 29 | } 30 | 31 | onSettingsChanged() { 32 | this.output = ""; 33 | 34 | const isInputValid = !!this.input && !!this.foregroundChar && !!this.backgroundChar; 35 | if (isInputValid) { 36 | this.module.ccall( 37 | "display_ascii", 38 | "void", 39 | ["string", "string", "string", "string"], 40 | ["/src/app/wasm/text-to-ascii/text-to-ascii.font.txt", this.input, this.foregroundChar, this.backgroundChar] 41 | ); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/app/wasm/text-to-ascii/text-to-ascii.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Source code ported from https://github.com/DevonBernard/ASCII-Art 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | // Helper function to read the provided font from a file. The format 16 | // of the font file is described in comments below. The width, 17 | // height, and bitmap_letters variables are set by this function. 18 | void ReadFont(const std::string &font_file, int &width, int &height, std::vector > &bitmap_letters) { 19 | // open the font file for reading 20 | std::ifstream istr(font_file.c_str()); 21 | if (!istr) { 22 | std::cerr << "ERROR: cannot open font file " << font_file << std::endl; 23 | return; 24 | } 25 | 26 | // read in the width & height for every character in the file 27 | istr >> width >> height; 28 | assert(width >= 1); 29 | assert(height >= 1); 30 | 31 | // Create a vector to store all 256 ASCII characters of the 32 | // characters. Each character is represented as a vector of 33 | // strings that are each wide. Initially the 34 | // characters are unknown (represented with the '?' character). 35 | bitmap_letters = std::vector > 36 | (256, std::vector(height, std::string(width, '?'))); 37 | 38 | // read in all the characters 39 | // output_type is the ascii integer representation of the character 40 | int ascii; 41 | while (istr >> ascii) { 42 | assert(ascii >= 0 && ascii < 256); 43 | // next the character is printed in single quotes 44 | char c; 45 | istr >> c; 46 | assert(c == '\''); 47 | 48 | // use std::noskipws to make sure we can read the space character correctly 49 | istr >> std::noskipws >> c; 50 | 51 | // verify that the ascii code matches the character 52 | assert(c == (char)ascii); 53 | 54 | // switch back to std::skipws mode 55 | istr >> std::skipws >> c; 56 | assert(c == '\''); 57 | 58 | // read in the letter 59 | std::vector bitmap; 60 | std::string tmp; 61 | for (int i = 0; i < height; i++) { 62 | istr >> tmp; 63 | assert((int)tmp.size() == width); 64 | 65 | // make sure the letter uses only '#' and '.' characters 66 | for (unsigned int j = 0; j < tmp.size(); j++) { 67 | assert(tmp[j] == '.' || tmp[j] == '#'); 68 | } 69 | bitmap.push_back(tmp); 70 | } 71 | 72 | // overwrite the initially unknown letter in the vector 73 | bitmap_letters[ascii] = bitmap; 74 | } 75 | } 76 | 77 | void find_characters(std::string &read_file_line, char &foreground_character, char &background_character) { 78 | int array[255] = { 0 }; // initialize all elements to 0 79 | std::vector str; 80 | 81 | // Add all characters from the user-entered ASCII file to an array 82 | for (int a = 0; a < read_file_line.size(); a++) { 83 | str.push_back(2); 84 | str[a] = read_file_line[a]; 85 | } 86 | 87 | int i, max_similar_character_count; 88 | 89 | // Count the occurance of every character in user-eneted ASCII file 90 | for (i = 0; str[i] != 0; i++) { 91 | ++array[str[i]]; 92 | } 93 | 94 | // Find the character that was most frequent and set as the background character 95 | // and find the second most frequently used character and set as the foreground character 96 | max_similar_character_count = array[0]; 97 | foreground_character = 0; 98 | for (i = 0; str[i] != 0; i++) { 99 | if (array[str[i]] > max_similar_character_count) { 100 | max_similar_character_count = array[str[i]]; 101 | background_character = str[i]; 102 | } 103 | else { 104 | if (background_character != str[i]) { 105 | foreground_character = str[i]; 106 | } 107 | } 108 | } 109 | } 110 | 111 | std::string display(std::string bitmap_file, std::string input, char foreground_character, char background_character) { 112 | std::string return_string = ""; 113 | int width = 6; 114 | int height = 7; 115 | std::vector > bitmap_letters; 116 | ReadFont(bitmap_file, width, height, bitmap_letters); 117 | int letter_index = 0; 118 | 119 | // START create the ASCII code for each letter. 120 | for (int row = 0; row < 7; row++) { 121 | letter_index = 0; 122 | 123 | // START convert simple_font format to user-entered format. 124 | for (std::string::iterator it = input.begin(); it != input.end(); ++it) { 125 | for (int letter_character_index = 0; letter_character_index < 7; ++letter_character_index) { 126 | if (bitmap_letters[input[letter_index]][row][letter_character_index] == '#') 127 | bitmap_letters[input[letter_index]][row][letter_character_index] = foreground_character; 128 | if (bitmap_letters[input[letter_index]][row][letter_character_index] == '.') 129 | bitmap_letters[input[letter_index]][row][letter_character_index] = background_character; 130 | } 131 | 132 | return_string = return_string + bitmap_letters[input[letter_index]][row] + background_character; 133 | letter_index++; 134 | } 135 | // END convert simple_font format to user-entered format. 136 | 137 | if (row < 6) { 138 | return_string = return_string + "\n"; 139 | } 140 | } 141 | // END create ASCII code. 142 | 143 | return return_string; 144 | } 145 | 146 | extern "C" { 147 | 148 | EMSCRIPTEN_KEEPALIVE 149 | void display_ascii(char* bitmap_file, char* input, char* foreground_character, char* background_character) { 150 | std::string output_string = display(std::string(bitmap_file), std::string(input), char(foreground_character[0]), char(background_character[0])); 151 | std::cout << output_string << std::endl; 152 | } 153 | 154 | } -------------------------------------------------------------------------------- /src/app/wasm/text-to-ascii/text-to-ascii.font.txt: -------------------------------------------------------------------------------- 1 | 6 7 2 | 32 ' ' 3 | ...... 4 | ...... 5 | ...... 6 | ...... 7 | ...... 8 | ...... 9 | ...... 10 | 33 '!' 11 | #..... 12 | #..... 13 | #..... 14 | #..... 15 | ...... 16 | #..... 17 | ...... 18 | 34 '"' 19 | #..#.. 20 | #..#.. 21 | ...... 22 | ...... 23 | ...... 24 | ...... 25 | ...... 26 | 35 '#' 27 | .#..#. 28 | ###### 29 | .#..#. 30 | .#..#. 31 | ###### 32 | .#..#. 33 | ...... 34 | 36 '$' 35 | ..#... 36 | #####. 37 | #.#... 38 | #####. 39 | ..#.#. 40 | #####. 41 | ..#... 42 | 37 '%' 43 | ##...# 44 | ##..#. 45 | ...#.. 46 | ..#... 47 | .#..## 48 | #...## 49 | ...... 50 | 38 '&' 51 | ..#... 52 | .#.#.. 53 | ..#... 54 | .#.#.# 55 | #...#. 56 | .###.# 57 | ...... 58 | 39 ''' 59 | .#.... 60 | #..... 61 | ...... 62 | ...... 63 | ...... 64 | ...... 65 | ...... 66 | 40 '(' 67 | .#.... 68 | #..... 69 | #..... 70 | #..... 71 | #..... 72 | .#.... 73 | ...... 74 | 41 ')' 75 | #..... 76 | .#.... 77 | .#.... 78 | .#.... 79 | .#.... 80 | #..... 81 | ...... 82 | 42 '*' 83 | ...... 84 | .#.#.. 85 | ..#... 86 | #####. 87 | ..#... 88 | .#.#.. 89 | ...... 90 | 43 '+' 91 | ...... 92 | ..#... 93 | ..#... 94 | #####. 95 | ..#... 96 | ..#... 97 | ...... 98 | 44 ',' 99 | ...... 100 | ...... 101 | ...... 102 | ...... 103 | .#.... 104 | .#.... 105 | #..... 106 | 45 '-' 107 | ...... 108 | ...... 109 | ...... 110 | #####. 111 | ...... 112 | ...... 113 | ...... 114 | 46 '.' 115 | ...... 116 | ...... 117 | ...... 118 | ...... 119 | ##.... 120 | ##.... 121 | ...... 122 | 47 '/' 123 | .....# 124 | ....#. 125 | ...#.. 126 | ..#... 127 | .#.... 128 | #..... 129 | ...... 130 | 48 '0' 131 | .####. 132 | #...## 133 | #..#.# 134 | #.#..# 135 | ##...# 136 | .####. 137 | ...... 138 | 49 '1' 139 | ..#... 140 | .##... 141 | ..#... 142 | ..#... 143 | ..#... 144 | #####. 145 | ...... 146 | 50 '2' 147 | .####. 148 | #....# 149 | .....# 150 | .####. 151 | #..... 152 | ###### 153 | ...... 154 | 51 '3' 155 | .####. 156 | #....# 157 | ...##. 158 | .....# 159 | #....# 160 | .####. 161 | ...... 162 | 52 '4' 163 | ...#.. 164 | ..##.. 165 | .#.#.. 166 | #..#.. 167 | ###### 168 | ...#.. 169 | ...... 170 | 53 '5' 171 | ###### 172 | #..... 173 | #####. 174 | .....# 175 | #....# 176 | .####. 177 | ...... 178 | 54 '6' 179 | .####. 180 | #..... 181 | #####. 182 | #....# 183 | #....# 184 | .####. 185 | ...... 186 | 55 '7' 187 | ###### 188 | .....# 189 | ....#. 190 | ...#.. 191 | ..#... 192 | ..#... 193 | ...... 194 | 56 '8' 195 | .####. 196 | #....# 197 | .####. 198 | #....# 199 | #....# 200 | .####. 201 | ...... 202 | 57 '9' 203 | .####. 204 | #....# 205 | #....# 206 | .##### 207 | .....# 208 | .####. 209 | ...... 210 | 58 ':' 211 | ...... 212 | ##.... 213 | ##.... 214 | ...... 215 | ##.... 216 | ##.... 217 | ...... 218 | 59 ';' 219 | ...... 220 | .#.... 221 | ...... 222 | .#.... 223 | .#.... 224 | #..... 225 | ...... 226 | 60 '<' 227 | ...... 228 | ..#... 229 | .#.... 230 | #..... 231 | .#.... 232 | ..#... 233 | ...... 234 | 61 '=' 235 | ...... 236 | ...... 237 | ####.. 238 | ...... 239 | ####.. 240 | ...... 241 | ...... 242 | 62 '>' 243 | ...... 244 | #..... 245 | .#.... 246 | ..#... 247 | .#.... 248 | #..... 249 | ...... 250 | 63 '?' 251 | .####. 252 | #....# 253 | ....#. 254 | ...#.. 255 | ...... 256 | ...#.. 257 | ...... 258 | 64 '@' 259 | .####. 260 | #..#.# 261 | #.#.## 262 | #.#### 263 | #..... 264 | .####. 265 | ...... 266 | 65 'A' 267 | .####. 268 | #....# 269 | #....# 270 | ###### 271 | #....# 272 | #....# 273 | ...... 274 | 66 'B' 275 | #####. 276 | #....# 277 | #####. 278 | #....# 279 | #....# 280 | #####. 281 | ...... 282 | 67 'C' 283 | .####. 284 | #....# 285 | #..... 286 | #..... 287 | #....# 288 | .####. 289 | ...... 290 | 68 'D' 291 | ####.. 292 | #...#. 293 | #....# 294 | #....# 295 | #...#. 296 | ####.. 297 | ...... 298 | 69 'E' 299 | ###### 300 | #..... 301 | #####. 302 | #..... 303 | #..... 304 | ###### 305 | ...... 306 | 70 'F' 307 | ###### 308 | #..... 309 | #####. 310 | #..... 311 | #..... 312 | #..... 313 | ...... 314 | 71 'G' 315 | .####. 316 | #....# 317 | #..... 318 | #..### 319 | #....# 320 | .####. 321 | ...... 322 | 72 'H' 323 | #....# 324 | #....# 325 | ###### 326 | #....# 327 | #....# 328 | #....# 329 | ...... 330 | 73 'I' 331 | #####. 332 | ..#... 333 | ..#... 334 | ..#... 335 | ..#... 336 | #####. 337 | ...... 338 | 74 'J' 339 | .....# 340 | .....# 341 | .....# 342 | #....# 343 | #....# 344 | .####. 345 | ...... 346 | 75 'K' 347 | #...#. 348 | #..#.. 349 | ###... 350 | #..#.. 351 | #...#. 352 | #....# 353 | ...... 354 | 76 'L' 355 | #..... 356 | #..... 357 | #..... 358 | #..... 359 | #..... 360 | ###### 361 | ...... 362 | 77 'M' 363 | #....# 364 | ##..## 365 | #.##.# 366 | #....# 367 | #....# 368 | #....# 369 | ...... 370 | 78 'N' 371 | #....# 372 | ##...# 373 | #.#..# 374 | #..#.# 375 | #...## 376 | #....# 377 | ...... 378 | 79 'O' 379 | .####. 380 | #....# 381 | #....# 382 | #....# 383 | #....# 384 | .####. 385 | ...... 386 | 80 'P' 387 | #####. 388 | #....# 389 | #....# 390 | #####. 391 | #..... 392 | #..... 393 | ...... 394 | 81 'Q' 395 | .####. 396 | #....# 397 | #....# 398 | #.#..# 399 | #..#.# 400 | .####. 401 | ...... 402 | 82 'R' 403 | #####. 404 | #....# 405 | #....# 406 | #####. 407 | #...#. 408 | #....# 409 | ...... 410 | 83 'S' 411 | .####. 412 | #..... 413 | .####. 414 | .....# 415 | #....# 416 | .####. 417 | ...... 418 | 84 'T' 419 | #####. 420 | ..#... 421 | ..#... 422 | ..#... 423 | ..#... 424 | ..#... 425 | ...... 426 | 85 'U' 427 | #....# 428 | #....# 429 | #....# 430 | #....# 431 | #....# 432 | .####. 433 | ...... 434 | 86 'V' 435 | #....# 436 | #....# 437 | #....# 438 | #....# 439 | .#..#. 440 | ..##.. 441 | ...... 442 | 87 'W' 443 | #....# 444 | #....# 445 | #....# 446 | #....# 447 | #.##.# 448 | .#..#. 449 | ...... 450 | 88 'X' 451 | #....# 452 | .#..#. 453 | ..##.. 454 | ..##.. 455 | .#..#. 456 | #....# 457 | ...... 458 | 89 'Y' 459 | #...#. 460 | .#.#.. 461 | ..#... 462 | ..#... 463 | ..#... 464 | ..#... 465 | ...... 466 | 90 'Z' 467 | ###### 468 | ....#. 469 | ...#.. 470 | ..#... 471 | .#.... 472 | ###### 473 | ...... 474 | 91 '[' 475 | ###... 476 | #..... 477 | #..... 478 | #..... 479 | #..... 480 | ###... 481 | ...... 482 | 92 '\' 483 | #..... 484 | .#.... 485 | ..#... 486 | ...#.. 487 | ....#. 488 | .....# 489 | ...... 490 | 93 ']' 491 | ###... 492 | ..#... 493 | ..#... 494 | ..#... 495 | ..#... 496 | ###... 497 | ...... 498 | 94 '^' 499 | ..#... 500 | .#.#.. 501 | #...#. 502 | ...... 503 | ...... 504 | ...... 505 | ...... 506 | 95 '_' 507 | ...... 508 | ...... 509 | ...... 510 | ...... 511 | ...... 512 | #####. 513 | ...... 514 | 96 '`' 515 | #..... 516 | .#.... 517 | ...... 518 | ...... 519 | ...... 520 | ...... 521 | ...... 522 | 97 'a' 523 | ...... 524 | .###.. 525 | ....#. 526 | .####. 527 | #...#. 528 | .####. 529 | ...... 530 | 98 'b' 531 | #..... 532 | #..... 533 | ####.. 534 | #...#. 535 | #...#. 536 | ####.. 537 | ...... 538 | 99 'c' 539 | ...... 540 | .###.. 541 | #..... 542 | #..... 543 | #..... 544 | .###.. 545 | ...... 546 | 100 'd' 547 | ....#. 548 | ....#. 549 | .####. 550 | #...#. 551 | #...#. 552 | .####. 553 | ...... 554 | 101 'e' 555 | ...... 556 | .###.. 557 | #...#. 558 | ####.. 559 | #..... 560 | .####. 561 | ...... 562 | 102 'f' 563 | ..##.. 564 | .#.... 565 | ###... 566 | .#.... 567 | .#.... 568 | .#.... 569 | ...... 570 | 103 'g' 571 | ...... 572 | .####. 573 | #...#. 574 | #...#. 575 | .####. 576 | ....#. 577 | .###.. 578 | 104 'h' 579 | #..... 580 | #..... 581 | ####.. 582 | #...#. 583 | #...#. 584 | #...#. 585 | ...... 586 | 105 'i' 587 | .#.... 588 | ...... 589 | ##.... 590 | .#.... 591 | .#.... 592 | ###... 593 | ...... 594 | 106 'j' 595 | ...#.. 596 | ...... 597 | ...#.. 598 | ...#.. 599 | ...#.. 600 | #..#.. 601 | .##... 602 | 107 'k' 603 | #..... 604 | #.#... 605 | ##.... 606 | ##.... 607 | #.#... 608 | #..#.. 609 | ...... 610 | 108 'l' 611 | #..... 612 | #..... 613 | #..... 614 | #..... 615 | #..... 616 | .##... 617 | ...... 618 | 109 'm' 619 | ...... 620 | ##.#.. 621 | #.#.#. 622 | #.#.#. 623 | #.#.#. 624 | #.#.#. 625 | ...... 626 | 110 'n' 627 | ...... 628 | ####.. 629 | #...#. 630 | #...#. 631 | #...#. 632 | #...#. 633 | ...... 634 | 111 'o' 635 | ...... 636 | .###.. 637 | #...#. 638 | #...#. 639 | #...#. 640 | .###.. 641 | ...... 642 | 112 'p' 643 | ...... 644 | #####. 645 | #....# 646 | #....# 647 | #####. 648 | #..... 649 | #..... 650 | 113 'q' 651 | ...... 652 | .####. 653 | #...#. 654 | #...#. 655 | .####. 656 | ....#. 657 | ....## 658 | 114 'r' 659 | ...... 660 | .###.. 661 | #..... 662 | #..... 663 | #..... 664 | #..... 665 | ...... 666 | 115 's' 667 | ...... 668 | .###.. 669 | #..... 670 | .###.. 671 | ....#. 672 | ####.. 673 | ...... 674 | 116 't' 675 | ...... 676 | .#.... 677 | ###... 678 | .#.... 679 | .#.... 680 | ..##.. 681 | ...... 682 | 117 'u' 683 | ...... 684 | #...#. 685 | #...#. 686 | #...#. 687 | #...#. 688 | .###.. 689 | ...... 690 | 118 'v' 691 | ...... 692 | #...#. 693 | #...#. 694 | .#.#.. 695 | .#.#.. 696 | ..#... 697 | ...... 698 | 119 'w' 699 | ...... 700 | #...#. 701 | #.#.#. 702 | #.#.#. 703 | #.#.#. 704 | .#.#.. 705 | ...... 706 | 120 'x' 707 | ...... 708 | #...#. 709 | .#.#.. 710 | ..#... 711 | .#.#.. 712 | #...#. 713 | ...... 714 | 121 'y' 715 | ...... 716 | #...#. 717 | #...#. 718 | #...#. 719 | .####. 720 | ....#. 721 | .###.. 722 | 122 'z' 723 | ...... 724 | #####. 725 | ...#.. 726 | ..#... 727 | .#.... 728 | #####. 729 | ...... 730 | 123 '{' 731 | ..###. 732 | ..#... 733 | ##.... 734 | ..#... 735 | ..#... 736 | ..###. 737 | ...... 738 | 124 '|' 739 | #..... 740 | #..... 741 | #..... 742 | #..... 743 | #..... 744 | #..... 745 | ...... 746 | 125 '}' 747 | ###... 748 | ..#... 749 | ...##. 750 | ..#... 751 | ..#... 752 | ###... 753 | ...... 754 | 126 '~' 755 | ...... 756 | .##..# 757 | #..##. 758 | ...... 759 | ...... 760 | ...... 761 | ...... 762 | 763 | -------------------------------------------------------------------------------- /src/app/wasm/tools.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Loads a JavaScript script async into the page 3 | * 4 | * @param id the HTML id for the script 5 | * @param url the URL to the generated JavaScript loader 6 | */ 7 | export function loadScript(id: string, url: string): Promise { 8 | let script = document.getElementById(id); 9 | if (script) { 10 | return Promise.resolve(); 11 | } 12 | 13 | return new Promise((resolve, reject) => { 14 | script = document.createElement("script"); 15 | document.body.appendChild(script); 16 | 17 | script.onload = () => resolve(); 18 | script.onerror = (ev: ErrorEvent) => reject(ev.error); 19 | script.id = id; 20 | script.async = true; 21 | script.src = url; 22 | }); 23 | } 24 | 25 | /** 26 | * Reads a string from a HEAP 27 | * 28 | * @param heap the HEAP 29 | * @param offset the offset 30 | */ 31 | export function utf8ToString(heap: Uint8Array, offset: number) { 32 | let s = ""; 33 | for (let i = offset; heap[i]; i++) { 34 | s += String.fromCharCode(heap[i]); 35 | } 36 | return s; 37 | } 38 | -------------------------------------------------------------------------------- /src/app/wasm/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-types */ 2 | /** 3 | * This is a simple typings definition for Emscripten's Module object 4 | */ 5 | interface EmscriptenReadFileOptions { 6 | encoding?: "utf8" | "binary"; 7 | flags?: string; 8 | } 9 | 10 | interface CcallOptions { 11 | async?: boolean; 12 | } 13 | 14 | interface EmscriptenModule { 15 | arguments?: string[]; 16 | print?(what: string): void; 17 | printErr?(what: string): void; 18 | locateFile?(file: string): string; 19 | ccall?( 20 | funcName: string, 21 | returnType: string, 22 | argumentTypes: string[], 23 | arguments: unknown[], 24 | options?: CcallOptions 25 | ): T; 26 | preRun?: Function[]; 27 | postRun?: Function[]; 28 | canvas?: HTMLCanvasElement; 29 | FS_createDataFile?( 30 | parent: string, 31 | name: string, 32 | data: string | Uint8Array, 33 | canRead?: boolean, 34 | canWrite?: boolean, 35 | canOwn?: boolean 36 | ): void; 37 | FS_createPreloadedFile?(parent: string, name: string, url: string, canRead?: boolean, canWrite?: boolean): void; 38 | FS_readFile?(url: string, options?: EmscriptenReadFileOptions): string; 39 | FS_unlink?(path: string): void; 40 | } 41 | -------------------------------------------------------------------------------- /src/assets/img/3d-cube/angular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boyanio/angular-wasm/843e6d8192b3286c43851e5c782e24b6755fb59d/src/assets/img/3d-cube/angular.png -------------------------------------------------------------------------------- /src/assets/img/3d-cube/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boyanio/angular-wasm/843e6d8192b3286c43851e5c782e24b6755fb59d/src/assets/img/3d-cube/cat.png -------------------------------------------------------------------------------- /src/assets/img/3d-cube/embroidery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boyanio/angular-wasm/843e6d8192b3286c43851e5c782e24b6755fb59d/src/assets/img/3d-cube/embroidery.png -------------------------------------------------------------------------------- /src/assets/img/angular-wasm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boyanio/angular-wasm/843e6d8192b3286c43851e5c782e24b6755fb59d/src/assets/img/angular-wasm.png -------------------------------------------------------------------------------- /src/assets/img/ascii/angular.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boyanio/angular-wasm/843e6d8192b3286c43851e5c782e24b6755fb59d/src/assets/img/ascii/angular.bmp -------------------------------------------------------------------------------- /src/assets/img/ascii/heart.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boyanio/angular-wasm/843e6d8192b3286c43851e5c782e24b6755fb59d/src/assets/img/ascii/heart.bmp -------------------------------------------------------------------------------- /src/assets/img/ascii/hello.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boyanio/angular-wasm/843e6d8192b3286c43851e5c782e24b6755fb59d/src/assets/img/ascii/hello.bmp -------------------------------------------------------------------------------- /src/assets/img/person-record/age.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/img/person-record/height.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/img/person-record/weight.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | wasmAssetsPath: "/angular-wasm/assets/wasm", 4 | }; 5 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: false, 3 | wasmAssetsPath: "/assets/wasm", 4 | }; 5 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Angular & WebAssembly 8 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 |
Loading...
39 |
40 |
41 | 42 | 43 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode, importProvidersFrom } from "@angular/core"; 2 | 3 | import { environment } from "./environments/environment"; 4 | import { bootstrapApplication } from "@angular/platform-browser"; 5 | import { AppComponent } from "./app/app.component"; 6 | import { provideRouter } from "@angular/router"; 7 | import { routes } from "./app/routes"; 8 | import { LaddaModule } from "angular2-ladda"; 9 | import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; 10 | 11 | if (environment.production) { 12 | enableProdMode(); 13 | } 14 | 15 | bootstrapApplication(AppComponent, { 16 | providers: [ 17 | provideRouter(routes), 18 | provideHttpClient(withInterceptorsFromDi()), 19 | importProvidersFrom( 20 | LaddaModule.forRoot({ 21 | style: "zoom-in", 22 | spinnerSize: 30, 23 | }) 24 | ), 25 | ], 26 | }).catch((err) => console.log(err)); 27 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import "zone.js"; // Included with Angular CLI. 49 | 50 | /*************************************************************************************************** 51 | * APPLICATION IMPORTS 52 | */ 53 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | } 5 | 6 | body { 7 | font-family: "Trebuchet MS", Helvetica, sans-serif; 8 | } 9 | 10 | h1, 11 | h2, 12 | h3, 13 | h4, 14 | h5, 15 | h6 { 16 | font-weight: 700; 17 | } 18 | 19 | a { 20 | color: #18bc9c; 21 | } 22 | 23 | a:focus, 24 | a:hover, 25 | a:active { 26 | color: #128f76; 27 | } 28 | 29 | .btn { 30 | border: 0; 31 | } 32 | 33 | .bg-primary { 34 | background-color: #18bc9c !important; 35 | } 36 | 37 | .bg-secondary { 38 | background-color: #2c3e50 !important; 39 | } 40 | 41 | .text-primary { 42 | color: #18bc9c !important; 43 | } 44 | 45 | .text-secondary { 46 | color: #2c3e50 !important; 47 | } 48 | 49 | .btn-primary { 50 | background-color: #18bc9c; 51 | border-color: #18bc9c; 52 | } 53 | 54 | .btn-primary:hover, 55 | .btn-primary:focus, 56 | .btn-primary:active { 57 | background-color: #128f76; 58 | border-color: #128f76; 59 | box-shadow: none; 60 | border: 0; 61 | } 62 | 63 | .btn-secondary { 64 | background-color: #2c3e50; 65 | border-color: #2c3e50; 66 | } 67 | 68 | .btn-secondary:hover, 69 | .btn-secondary:focus, 70 | .btn-secondary:active { 71 | background-color: #1a252f; 72 | border-color: #1a252f; 73 | } 74 | 75 | .demo-contents { 76 | border: 2px dotted #18bc9c; 77 | padding: 10px; 78 | margin-bottom: 50px; 79 | } 80 | 81 | .app-loader { 82 | text-align: center; 83 | height: 100%; 84 | width: 100%; 85 | display: table; 86 | } 87 | 88 | .app-loader .container { 89 | display: table-cell; 90 | height: 100%; 91 | vertical-align: middle; 92 | } 93 | 94 | .ladda-button { 95 | padding: 0.37rem 0.75rem; 96 | font-size: 1rem; 97 | border-radius: 0.25rem; 98 | } 99 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": ["src/main.ts", "src/polyfills.ts"], 8 | "include": ["src/**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "esModuleInterop": true, 9 | "experimentalDecorators": true, 10 | "module": "es2020", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "resolveJsonModule": true, 14 | "target": "ES2022", 15 | "lib": ["es2018", "dom"], 16 | "useDefineForClassFields": false 17 | }, 18 | "angularCompilerOptions": { 19 | "fullTemplateTypeCheck": true, 20 | "strictInjectionParameters": true 21 | } 22 | } 23 | --------------------------------------------------------------------------------