Compare commits

..

No commits in common. "main" and "stable-1.1" have entirely different histories.

108 changed files with 7015 additions and 13795 deletions

57
.angular-cli.json Normal file
View File

@ -0,0 +1,57 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"project": {
"name": "convertorizr"
},
"apps": [
{
"root": "src",
"outDir": "dist",
"assets": [
"assets",
"favicon.ico"
],
"index": "index.html",
"main": "main.ts",
"polyfills": "polyfills.ts",
"test": "test.ts",
"tsconfig": "tsconfig.app.json",
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"styles": [
"styles.scss"
],
"scripts": [],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
}
],
"e2e": {
"protractor": {
"config": "./protractor.conf.js"
}
},
"lint": [
{
"project": "src/tsconfig.app.json"
},
{
"project": "src/tsconfig.spec.json"
},
{
"project": "e2e/tsconfig.e2e.json"
}
],
"test": {
"karma": {
"config": "./karma.conf.js"
}
},
"defaults": {
"styleExt": "scss",
"component": {}
}
}

View File

@ -1,12 +0,0 @@
kind: pipeline
type: docker
name: default
steps:
- name: install
image: node:20-alpine
commands:
- apk add firefox
- npm install
- npm run test:ci
- npm run build

View File

@ -1,4 +1,4 @@
# Editor configuration, see https://editorconfig.org # Editor configuration, see http://editorconfig.org
root = true root = true
[*] [*]
@ -8,9 +8,6 @@ indent_size = 2
insert_final_newline = true insert_final_newline = true
trim_trailing_whitespace = true trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md] [*.md]
max_line_length = off max_line_length = off
trim_trailing_whitespace = false trim_trailing_whitespace = false

28
.gitignore vendored
View File

@ -1,18 +1,16 @@
# See http://help.github.com/ignore-files/ for more about ignoring files. # See http://help.github.com/ignore-files/ for more about ignoring files.
# Compiled output # compiled output
/dist /dist
/tmp /tmp
/out-tsc /out-tsc
/bazel-out /src/**/*.js
# Node # dependencies
/node_modules /node_modules
npm-debug.log
yarn-error.log
# IDEs and editors # IDEs and editors
.idea/ /.idea
.project .project
.classpath .classpath
.c9/ .c9/
@ -20,23 +18,27 @@ yarn-error.log
.settings/ .settings/
*.sublime-workspace *.sublime-workspace
# Visual Studio Code # IDE - VSCode
.vscode/tasks.json .vscode/*
!.vscode/settings.json !.vscode/settings.json
!.vscode/tasks.json !.vscode/tasks.json
!.vscode/launch.json !.vscode/launch.json
!.vscode/extensions.json !.vscode/extensions.json
.history/*
# Miscellaneous # misc
/.angular/cache /.sass-cache
.sass-cache/
/connect.lock /connect.lock
/coverage /coverage
/libpeerconnection.log /libpeerconnection.log
npm-debug.log
testem.log testem.log
/typings /typings
# System files # e2e
/e2e/*.js
/e2e/*.map
# System Files
.DS_Store .DS_Store
Thumbs.db Thumbs.db
*~

78
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,78 @@
stages:
- build
- cleanup_build
- deploy
- cleanup
build_job:
stage: build
script:
- yarn install
- yarn run build
- yarn run lint
# - yarn run test
# - yarn run e2e
tags:
- javascript
except:
- tags
- master
artifacts:
paths:
- dist
expire_in: 30 min
build_job_production:
stage: build
script:
- yarn install
- yarn run build-prod
- yarn run lint
# - yarn run test
# - yarn run e2e
tags:
- javascript
only:
- master
artifacts:
paths:
- dist
expire_in: 30 min
cleanup_build_job:
stage: cleanup_build
script:
- rm -rf node_modules
- rm -rf dist
when: on_failure
pages:
stage: deploy
environment: staging
except:
- tags
- master
script:
- mv dist public
artifacts:
paths:
- public
dependencies:
- build_job
production:
stage: deploy
environment: production
only:
- master
script:
- if [[ -z "${WWW_DEPLOY_ROOT_PRODUCTION}" ]] ; then echo "WWW_DEPLOY_ROOT_PRODUCTION is not set" ; exit 1 ; fi
- if [[ ! -d "${WWW_DEPLOY_ROOT_PRODUCTION}" ]] ; then mkdir -p "${WWW_DEPLOY_ROOT_PRODUCTION}" || die "Failed to create target directory for deployment!" ; fi
- cp -r dist/* "${WWW_DEPLOY_ROOT_PRODUCTION}"
cleanup_job:
stage: cleanup
script:
- rm -rf node_modules
when: always
allow_failure: true

View File

@ -1,29 +1,28 @@
# Convertorizr - Convert whatever you want! # convertorizr - Convert whatever you want!
This is a short introduction to the awesome Convertorizr hosted at https://conv.friedli.info/. This is a short introduction to the awesome Convertorizr hosted at [https://conv.friedli.info/].
Continuous integration is automated with Drone CI ([![Build Status](https://ci.gittr.ch/api/badges/manuel/converter/status.svg?ref=refs/heads/main)](https://ci.gittr.ch/manuel/converter)). Usage is self-explanatory. What else do you need to know? Deployment is automated with Gitlab CI. Usage is self-explanatory. What else do you need to know?
The source code is hosted at https://gittr.ch/manuel/converter.git. Contact the author at manuel-convertorizr|at|fritteli.ch.
Contact the author at manuel|at|fritteli.ch.
Cheers! Cheers!
# Technical stuff # Technical stuff
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.1.0. This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.0.
## Development server ## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding ## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class/module`.
## Build ## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
## Running unit tests ## Running unit tests
@ -31,8 +30,9 @@ Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.
## Running end-to-end tests ## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
Before running the tests make sure you are serving the app via `ng serve`.
## Further help ## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

View File

@ -1,102 +0,0 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"convertorizr": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/convertorizr",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.svg",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "convertorizr:build:production"
},
"development": {
"buildTarget": "convertorizr:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "convertorizr:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.svg",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": [],
"karmaConfig": "karma.conf.js"
}
}
}
}
}
}

14
e2e/app.e2e-spec.ts Normal file
View File

@ -0,0 +1,14 @@
import {ConvertorizrPage} from './app.po';
describe('convertorizr App', () => {
let page: ConvertorizrPage;
beforeEach(() => {
page = new ConvertorizrPage();
});
it('should display message saying app works', () => {
page.navigateTo();
expect(page.getInputfieldContent(0)).toEqual('');
});
});

25
e2e/app.po.ts Normal file
View File

@ -0,0 +1,25 @@
import {browser, by, element} from 'protractor';
export class ConvertorizrPage {
navigateTo() {
return browser.get('/');
}
public foo() {
return 'bar';
}
public getInputfieldContent(index: number): Promise<any> {
const css1 = by.css('app-root div.inputwrapper');
console.log(css1);
const el1 = element.all(css1)[index];
console.log(el1);
const css2 = by.css('.textwrapper textarea');
console.log(css2);
const el2 = el1.findElement(css2);
console.log(el2);
const t = el2.getText();
console.log(t);
return t;
}
}

12
e2e/tsconfig.e2e.json Normal file
View File

@ -0,0 +1,12 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es5",
"types":[
"jasmine",
"node"
]
}
}

View File

@ -1,40 +1,48 @@
// Karma configuration file, see link for more information // Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html // https://karma-runner.github.io/0.13/config/configuration-file.html
module.exports = function (config) { module.exports = function (config) {
config.set({ config.set({
basePath: '', basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'], frameworks: ['jasmine', '@angular/cli'],
plugins: [ plugins: [
require('karma-jasmine'), require('karma-jasmine'),
require('karma-chrome-launcher'), require('karma-phantomjs-launcher'),
require('karma-firefox-launcher'),
require('karma-jasmine-html-reporter'), require('karma-jasmine-html-reporter'),
require('karma-coverage'), require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma') require('@angular/cli/plugins/karma')
], ],
client: { client: {
jasmine: { captureConsole: true,
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false // leave Jasmine Spec Runner output visible in browser clearContext: false // leave Jasmine Spec Runner output visible in browser
}, },
jasmineHtmlReporter: { files: [
suppressAll: true // removes the duplicated traces {
pattern: './src/test.ts',
watched: false
}
],
preprocessors: {
'./src/test.ts': ['@angular/cli']
}, },
coverageReporter: { mime: {
dir: require('path').join(__dirname, './coverage/convertorizr'), 'text/x-typescript': ['ts', 'tsx']
subdir: '.',
reporters: [
{ type: 'html' },
{ type: 'text-summary' }
]
}, },
reporters: ['progress', 'kjhtml'], coverageIstanbulReporter: {
browsers: ['Firefox'], reports: ['html', 'lcovonly'],
restartOnFileChange: true fixWebpackSourcePaths: true
},
angularCli: {
environment: 'dev'
},
reporters: config.angularCli && config.angularCli.codeCoverage
? ['progress', 'coverage-istanbul']
: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['PhantomJS'],
singleRun: false
}); });
}; };

12000
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "convertorizr", "name": "convertorizr",
"version": "2.0.1-dev.0", "version": "1.1.0",
"description": "Decode or encode base64, punycode, HTML entities, URI components, ...", "description": "Decode or encode base64, punycode, HTML entities, URI components, ...",
"keywords": [ "keywords": [
"encode", "encode",
@ -14,52 +14,57 @@
"email": "manuel@fritteli.ch" "email": "manuel@fritteli.ch"
}, },
"license": "MIT", "license": "MIT",
"homepage": "https://conv.friedli.info/", "homepage": "https://manuel.pages.gittr.ch/dencode.org",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://gittr.ch/manuel/converter.git" "url": "https://gittr.ch/manuel/dencode.org.git"
}, },
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve", "start": "ng serve",
"build": "ng build", "build": "ng build",
"watch": "ng build --watch --configuration development", "build-prod": "ng build --env=prod",
"test": "ng test --browsers=Chromium,Firefox", "test": "ng test --single-run",
"test:ci": "ng test --no-watch --no-progress --browsers=FirefoxHeadless" "test-continuous": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^17.1.0", "@angular/common": "^4.0.0",
"@angular/common": "^17.1.0", "@angular/compiler": "^4.0.0",
"@angular/compiler": "^17.1.0", "@angular/core": "^4.0.0",
"@angular/core": "^17.1.0", "@angular/forms": "^4.0.0",
"@angular/forms": "^17.1.0", "@angular/http": "^4.0.0",
"@angular/platform-browser": "^17.1.0", "@angular/platform-browser": "^4.0.0",
"@angular/platform-browser-dynamic": "^17.1.0", "@angular/platform-browser-dynamic": "^4.0.0",
"@angular/router": "^17.1.0", "@angular/router": "^4.0.0",
"punycode": "^2.3.1", "core-js": "^2.4.1",
"quoted-printable": "^1.0.1", "punycode": "^2.1.0",
"rxjs": "~7.8.0", "quoted-printable": "^1.0.0",
"tslib": "^2.3.0", "rxjs": "^5.1.0",
"utf8": "^3.0.0", "utf8": "^2.1.0",
"zone.js": "~0.14.3" "zone.js": "^0.8.4"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^17.1.0", "@angular/cli": "^1.0.0",
"@angular/cli": "^17.1.0", "@angular/compiler-cli": "^4.0.0",
"@angular/compiler-cli": "^17.1.0", "@types/jasmine": "^2.5.38",
"@types/jasmine": "~5.1.0", "@types/node": "^7.0.0",
"@types/node": "^20.11.5", "codelyzer": "^2.0.0",
"@types/punycode": "^2.1.3", "jasmine-core": "^2.5.2",
"@types/quoted-printable": "^1.0.2", "jasmine-spec-reporter": "^4.0.0",
"@types/utf8": "^3.0.3", "karma": "^1.4.1",
"jasmine-core": "~5.1.0", "karma-chrome-launcher": "^2.0.0",
"karma": "~6.4.0", "karma-cli": "^1.0.1",
"karma-chrome-launcher": "~3.2.0", "karma-coverage-istanbul-reporter": "^1.2.0",
"karma-coverage": "~2.2.0", "karma-jasmine": "^1.1.0",
"karma-firefox-launcher": "^2.1.2", "karma-jasmine-html-reporter": "^0.2.2",
"karma-jasmine": "~5.1.0", "karma-phantomjs-launcher": "^1.0.4",
"karma-jasmine-html-reporter": "~2.1.0", "protractor": "^5.1.0",
"typescript": "~5.3.2" "protractor-console": "^2.0.1",
"ts-node": "^3.0.0",
"tslint": "^4.0.0",
"typescript": "~2.2.0"
} }
} }

35
protractor.conf.js Normal file
View File

@ -0,0 +1,35 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const {SpecReporter} = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./e2e/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function () {
}
},
beforeLaunch: function () {
require('ts-node').register({
project: 'e2e/tsconfig.e2e.json'
});
},
onPrepare() {
jasmine.getEnv().addReporter(new SpecReporter({spec: {displayStacktrace: true}}));
},
plugins: [{
package: 'protractor-console',
logLevels: ['debug', 'info', 'warning', 'severe']
}]
};

View File

@ -0,0 +1,16 @@
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
const routes: Routes = [
{
path: '',
children: []
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {
}

View File

@ -1,10 +1,17 @@
@for (step of steps; track step.index) { <div *ngFor="let step of steps" class="inputwrapper">
<div class="inputwrapper"> <div class="textwrapper arrow_box">
<app-text-input-field [step]="step" #ti></app-text-input-field> <textarea class="textinput" (keyup)="update(step)" placeholder="Please enter your input ..."
<app-converter-selector [step]="step" [textInput]="ti"></app-converter-selector> [(ngModel)]="step.content">{{step.content}}</textarea>
<app-error-message [step]="step"></app-error-message>
</div> </div>
} <div [ngClass]="{selectwrapper: true, error: step.error}">
<app-version></app-version> <div class="arrow_box">
<div class="contact">Wanna say something? <a href="mailto:manuel@fritteli.ch?subject=Contact%20via%20Convertorizr">Tell me!</a></div> <select class="select" (change)="convert(step, $event)">
<option id="undefined">Select conversion ...</option>
<option class="option" *ngFor="let c of converters" id="{{c.getId()}}">{{c.getDisplayname()}}
</option>
</select>
</div>
</div>
<div class="errormessage" *ngIf="step.error" [innerHTML]="step.message"></div>
</div>
<!--<router-outlet></router-outlet>--> <!--<router-outlet></router-outlet>-->

View File

@ -1,10 +1,107 @@
.contact {
font-size: small;
margin-right: 2em;
text-align: right;
}
.inputwrapper { .inputwrapper {
font-family: "ABeeZee", sans-serif; font-family: "ABeeZee", sans-serif;
margin: 0 1em 1em 1em; margin: 0 1em 1em 1em;
} }
.textwrapper {
margin: 0 0 1em 0;
padding: 0 1em 0 0;
}
.arrow_box {
position: relative;
background: #fff;
border: 1px solid #aaa;
&:focus {
border-color: #888;
}
&:hover {
border-color: #333;
}
&:after, &:before {
top: 100%;
left: 50%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}
&:after {
border-color: rgba(255, 255, 255, 0);
border-top-color: #fff;
border-width: 1em;
margin-left: -1em;
}
&:before {
border-color: rgba(170, 170, 170, 0);
border-top-color: #aaa;
border-width: calc(1em + 1px);
margin-left: calc(-1em - 1px);
}
&:focus:before {
border-color: rgba(136, 136, 136, 0);
border-top-color: #888;
}
&:hover:before {
border-color: rgba(51, 51, 51, 0);
border-top-color: #333;
}
.selectwrapper > & {
display: inline-block;
}
}
.textinput {
background-color: #fff;
border: none;
color: #000;
font-family: "Free Monospaced", monospace;
height: 10em;
margin: 0;
padding: 0.5em;
resize: vertical;
width: 100%;
&:focus {
border-color: #888;
}
&:hover {
border-color: #333;
}
}
.selectwrapper {
margin: 0 0 1em 0;
padding: 0;
text-align: center;
&.error {
> .arrow_box {
border-color: red;
&:before {
border-top-color: red;
}
}
select {
color: red;
}
}
}
.select {
background-color: #fff;
border: none;
color: #000;
font-family: "ABeeZee", sans-serif;
margin: 0;
padding: 0.5em;
}
.option {
/* font-family: "ABeeZee", sans-serif;*/
}
.errormessage {
color: red;
text-align: center;
}

View File

@ -1,5 +1,6 @@
import {AppComponent} from './app.component'; import {AppComponent} from './app.component';
import {ComponentFixture, TestBed} from '@angular/core/testing'; import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {InputComponentManagerService} from './inputcomponentmanager.service';
import {Step} from './step'; import {Step} from './step';
describe('AppComponent', () => { describe('AppComponent', () => {
@ -7,20 +8,47 @@ describe('AppComponent', () => {
let fixture: ComponentFixture<AppComponent>; let fixture: ComponentFixture<AppComponent>;
const firstStep: Step = new Step(0); const firstStep: Step = new Step(0);
beforeEach(async () => { const inputComponentManagerServiceStub = {
await TestBed.configureTestingModule({ getFirst: () => {
imports: [AppComponent] return firstStep;
}
};
beforeEach(async(() => {
/*return */
TestBed.configureTestingModule({
declarations: [AppComponent],
providers: [{
provide: InputComponentManagerService, useValue: inputComponentManagerServiceStub
}]
}) })
.compileComponents(); .compileComponents();
}); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(AppComponent); fixture = TestBed.createComponent(AppComponent);
sut = fixture.componentInstance; sut = fixture.componentInstance;
}); });
// beforeEach(async(() => {
// TestBed.configureTestingModule({
// imports: [
// RouterTestingModule
// ],
// declarations: [
// AppComponent
// ],
// }).compileComponents();
// }));
it('should create the app', () => { it('should be true that true is true', () => {
// const fixture = TestBed.createComponent(AppComponent); expect(true).toBe(true);
expect(sut).toBeTruthy();
}); });
// it('should create the app', async(() => {
// const fixture = TestBed.createComponent(AppComponent);
// const app = fixture.debugElement.componentInstance;
// expect(app).toBeTruthy();
// }));
}); });

View File

@ -1,32 +1,59 @@
import {Component, OnInit} from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {InputComponentManagerService} from './input-component-manager.service'; import {ConverterRegistryService} from './converterregistry.service';
import {InputComponentManagerService} from './inputcomponentmanager.service';
import {NativeLibraryWrapperService} from './nativelibrarywrapper.service';
import {Step} from './step'; import {Step} from './step';
import {RouterOutlet} from '@angular/router'; import {Converter} from './converter/converter';
import {TextInputFieldComponent} from './text-input-field/text-input-field.component';
import {ConverterSelectorComponent} from './converter-selector/converter-selector.component';
import {ErrorMessageComponent} from './error-message/error-message.component';
import {VersionComponent} from './version/version.component';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
standalone: true,
imports: [
ConverterSelectorComponent,
ErrorMessageComponent,
RouterOutlet,
TextInputFieldComponent,
VersionComponent
],
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrl: './app.component.scss' styleUrls: ['./app.component.scss'],
providers: [ConverterRegistryService, InputComponentManagerService, NativeLibraryWrapperService]
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit {
public steps: Step[] = []; public steps: Step[] = [];
public converters: Converter[] = [];
constructor(private inputComponentManagerService: InputComponentManagerService) { constructor(private converterRegistryService: ConverterRegistryService,
private inputComponentManagerService: InputComponentManagerService) {
}
convert(step: Step, $event: any): void {
step.selectedConverter = this.converterRegistryService.getConverter($event.target.selectedOptions[0].id);
this.update(step);
}
update(step: Step): void {
const converter: Converter = step.selectedConverter;
if (converter !== undefined) {
const content: string = step.content;
let result: string;
try {
result = converter.convert(content);
} catch (error) {
if (typeof console === 'object' && typeof console.log === 'function') {
console.log(error);
}
step.message = error.message;
step.error = true;
result = null;
}
if (result !== null) {
step.message = '';
step.error = false;
if (result !== '') {
const nextComponent: Step = this.inputComponentManagerService.getNext(step);
nextComponent.content = result;
this.update(nextComponent);
}
}
}
} }
ngOnInit(): void { ngOnInit(): void {
this.converters = this.converterRegistryService.getAllConverters();
this.steps = this.inputComponentManagerService.getAllComponents(); this.steps = this.inputComponentManagerService.getAllComponents();
this.inputComponentManagerService.getFirst(); this.inputComponentManagerService.getFirst();
} }

View File

@ -1,8 +0,0 @@
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes)]
};

23
src/app/app.module.ts Normal file
View File

@ -0,0 +1,23 @@
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {HttpModule} from '@angular/http';
import {AppRoutingModule} from './app-routing.module';
import {AppComponent} from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {
}

View File

@ -1,3 +0,0 @@
import { Routes } from '@angular/router';
export const routes: Routes = [];

View File

@ -1,40 +0,0 @@
import {inject, TestBed} from '@angular/core/testing';
import {ConverterRegistryService} from './converter-registry.service';
import {NativeLibraryWrapperService} from './native-library-wrapper.service';
import {Converter} from './converter/converter';
import {Base64Decoder} from './converter/base64-decoder';
import createSpyObj = jasmine.createSpyObj;
describe('ConverterRegistryService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
ConverterRegistryService,
{provide: NativeLibraryWrapperService, useValue: createSpyObj(['punycode', 'quotedPrintable', 'utf8'])}
]
});
});
it('should be created', inject([ConverterRegistryService], (service: ConverterRegistryService) => {
expect(service).toBeTruthy();
}));
it('should register converters upon creation', inject([ConverterRegistryService], (service: ConverterRegistryService) => {
expect(service.getAllConverters()).toBeTruthy();
expect(service.getAllConverters().length).toBeGreaterThan(0);
}));
it('must not allow the same converter ID to be regisgered more than once',
inject([ConverterRegistryService], (service: ConverterRegistryService) => {
// arrange
const duplicateConverter: Converter = new Base64Decoder();
const duplicateConverterId = duplicateConverter.getId();
expect(() => {
// act
(service as any).registerConverter(duplicateConverter);
})
// assert
.toThrowError(`Converter-ID ${duplicateConverterId} is already registered!`);
}));
});

View File

@ -1,72 +0,0 @@
import {Base64Decoder} from './converter/base64-decoder';
import {Base64Encoder} from './converter/base64-encoder';
import {BinToDecConverter} from './converter/bin-to-dec-converter';
import {Converter} from './converter/converter';
import {DecToBinConverter} from './converter/dec-to-bin-converter';
import {DecToHexConverter} from './converter/dec-to-hex-converter';
import {HexToDecConverter} from './converter/hex-to-dec-converter';
import {HTMLEntitiesDecoder} from './converter/htmlentities-decoder';
import {HTMLEntitiesEncoder} from './converter/htmlentities-encoder';
import {Injectable} from '@angular/core';
import {NativeLibraryWrapperService} from './native-library-wrapper.service';
import {PunycodeDecoder} from './converter/punycode-decoder';
import {PunycodeEncoder} from './converter/punycode-encoder';
import {QuotedPrintableDecoder} from './converter/quoted-printable-decoder';
import {QuotedPrintableEncoder} from './converter/quoted-printable-encoder';
import {ROT13Converter} from './converter/rot13-converter';
import {URIComponentDecoder} from './converter/uricomponent-decoder';
import {URIComponentEncoder} from './converter/uricomponent-encoder';
import {URIDecoder} from './converter/uridecoder';
import {URIEncoder} from './converter/uriencoder';
import {UTF8Decoder} from './converter/utf8-decoder';
import {UTF8Encoder} from './converter/utf8-encoder';
@Injectable({
providedIn: 'root'
})
export class ConverterRegistryService {
private converters: Converter[] = [];
constructor(private wrapper: NativeLibraryWrapperService) {
this.init();
}
public getAllConverters(): Converter[] {
return this.converters;
}
public getConverter(id: string): Converter | undefined {
return this.converters.find((converter: Converter): boolean => converter.getId() === id);
}
private init(): void {
this.registerConverter(new Base64Encoder());
this.registerConverter(new Base64Decoder());
this.registerConverter(new URIEncoder());
this.registerConverter(new URIDecoder());
this.registerConverter(new URIComponentEncoder());
this.registerConverter(new URIComponentDecoder());
this.registerConverter(new HTMLEntitiesEncoder());
this.registerConverter(new HTMLEntitiesDecoder());
this.registerConverter(new QuotedPrintableEncoder(this.wrapper));
this.registerConverter(new QuotedPrintableDecoder(this.wrapper));
this.registerConverter(new DecToHexConverter());
this.registerConverter(new HexToDecConverter());
this.registerConverter(new DecToBinConverter());
this.registerConverter(new BinToDecConverter());
this.registerConverter(new PunycodeEncoder(this.wrapper));
this.registerConverter(new PunycodeDecoder(this.wrapper));
this.registerConverter(new UTF8Encoder(this.wrapper));
this.registerConverter(new UTF8Decoder(this.wrapper));
this.registerConverter(new ROT13Converter());
}
private registerConverter(converter: Converter): void {
// Don't allow duplicate registration of the same converter id
if (this.converters.some((c: Converter): boolean => c.getId() === converter.getId())) {
throw new Error(`Converter-ID ${converter.getId()} is already registered!`);
}
this.converters.push(converter);
}
}

View File

@ -1,10 +0,0 @@
<div [ngClass]="{select_wrapper: true, error: step.error}">
<div class="arrow_box">
<select (change)="convert($event)">
<option id="undefined">Select conversion ...</option>
@for (c of converters; track c.getId()) {
<option id="{{c.getId()}}">{{ c.getDisplayname() }}</option>
}
</select>
</div>
</div>

View File

@ -1,84 +0,0 @@
.select_wrapper {
margin: 0 0 1em 0;
padding: 0;
text-align: center;
&.error {
> .arrow_box {
border-color: red;
&:before {
border-top-color: red;
}
}
select {
color: red;
transition: background-color ease-in-out 200ms, color ease-in-out 200ms;
}
}
}
select {
background-color: #222;
border: none;
color: #eee;
font-family: "ABeeZee", sans-serif;
margin: 0;
padding: 0.5em;
transition: background-color ease-in-out 200ms, color ease-in-out 200ms;
&:focus, &:hover {
background-color: #333;
color: #fff;
}
}
.arrow_box {
border: 1px solid #888;
position: relative;
transition: background-color ease-in-out 200ms, border ease-in-out 200ms;
&:after, &:before {
border: solid transparent;
content: " ";
height: 0;
left: 50%;
pointer-events: none;
position: absolute;
top: 100%;
transition: background-color ease-in-out 200ms, border ease-in-out 200ms;
width: 0;
}
&:after {
border-color: rgba(255, 255, 255, 0);
border-top-color: #222;
border-width: 1em;
margin-left: -1em;
}
&:before {
border-top-color: #888;
border-width: calc(1em + 1px);
margin-left: calc(-1em - 1px);
}
&:has(>select:focus, >select:hover) {
&:after {
border-top-color: #333;
}
&:before {
border-top-color: #bbb;
}
}
&:hover {
border-color: #bbb;
}
.select_wrapper > & {
display: inline-block;
}
}

View File

@ -1,93 +0,0 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {ConverterSelectorComponent} from './converter-selector.component';
import {Component, ViewChild} from '@angular/core';
import {Step} from '../step';
import {TextInputFieldComponent} from '../text-input-field/text-input-field.component';
import {InputComponentManagerService} from '../input-component-manager.service';
import {ConverterRegistryService} from '../converter-registry.service';
import {Converter} from '../converter/converter';
import createSpyObj = jasmine.createSpyObj;
import SpyObj = jasmine.SpyObj;
@Component({
template: '<app-converter-selector [step]="step" [textInput]="textInputComponent"></app-converter-selector>'
})
class TestHostComponent {
public step: Step = new Step(42);
public textInputComponent: TextInputFieldComponent = new TextInputFieldComponent(inputComponentManagerServiceStub);
@ViewChild(ConverterSelectorComponent)
public sutComponent!: ConverterSelectorComponent;
}
const inputComponentManagerServiceStub = createSpyObj(['getNext']);
describe('ConverterSelectorComponent', () => {
let testHostComponent: TestHostComponent;
let testHostFixture: ComponentFixture<TestHostComponent>;
const converter1: SpyObj<Converter> = createSpyObj(['getId', 'getDisplayname', 'convert']);
const converter2: SpyObj<Converter> = createSpyObj(['getId', 'getDisplayname', 'convert']);
const converter3: SpyObj<Converter> = createSpyObj(['getId', 'getDisplayname', 'convert']);
// converter1.getId.and.returnValue('converter1');
converter1.getDisplayname.and.returnValue('Converter 1');
// converter2.getId.and.returnValue('converter2');
converter2.getDisplayname.and.returnValue('Converter 2');
// converter3.getId.and.returnValue('converter3');
converter3.getDisplayname.and.returnValue('Converter 3');
const converterRegistryServiceStub = createSpyObj(['getAllConverters', 'getConverter']);
converterRegistryServiceStub.getAllConverters.and.returnValue([converter1, converter2, converter3]);
converterRegistryServiceStub.getConverter.and.returnValue(undefined);
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ConverterSelectorComponent],
declarations: [
TestHostComponent
],
providers: [
{provide: InputComponentManagerService, useValue: inputComponentManagerServiceStub},
{provide: ConverterRegistryService, useValue: converterRegistryServiceStub}
]
})
.compileComponents();
});
beforeEach(() => {
testHostFixture = TestBed.createComponent(TestHostComponent);
testHostComponent = testHostFixture.componentInstance;
testHostFixture.detectChanges();
});
it('should create the component', () => {
expect(testHostComponent.sutComponent).toBeTruthy();
});
it('should update the conversion when the selection changes', () => {
// arrange
// set next step
const nextStep: Step = new Step(99);
inputComponentManagerServiceStub.getNext.and.returnValue(nextStep);
// set up converter
const dummyConverter = createSpyObj(['convert']);
dummyConverter.convert.and.returnValue('Converted value');
converterRegistryServiceStub.getConverter.and.returnValue(dummyConverter);
// create event structure
const event = {target: {selectedOptions: [{id: 'The testing converter id'}]}};
// act
testHostComponent.sutComponent.convert(event);
// assert
expect(converterRegistryServiceStub.getConverter).toHaveBeenCalledWith('The testing converter id');
expect(nextStep.content).toEqual('Converted value');
});
it('should load available converters upon creation', () => {
const converters: Converter[] = testHostComponent.sutComponent.converters;
expect(converters.length).toEqual(3);
expect(converters[0]).toEqual(converter1);
expect(converters[1]).toEqual(converter2);
expect(converters[2]).toEqual(converter3);
});
});

View File

@ -1,33 +0,0 @@
import {Component, Input, OnInit} from '@angular/core';
import {Step} from '../step';
import {Converter} from '../converter/converter';
import {ConverterRegistryService} from '../converter-registry.service';
import {TextInputFieldComponent} from '../text-input-field/text-input-field.component';
import {CommonModule} from '@angular/common';
@Component({
selector: 'app-converter-selector',
templateUrl: './converter-selector.component.html',
standalone: true,
styleUrls: ['./converter-selector.component.scss'],
imports: [CommonModule]
})
export class ConverterSelectorComponent implements OnInit {
@Input()
public step!: Step;
@Input()
textInput!: TextInputFieldComponent;
public converters: Converter[] = [];
constructor(private converterRegistryService: ConverterRegistryService) {
}
convert($event: any): void {
this.step.selectedConverter = this.converterRegistryService.getConverter($event.target.selectedOptions[0].id);
this.textInput.update(this.step);
}
ngOnInit() {
this.converters = this.converterRegistryService.getAllConverters();
}
}

View File

@ -1,27 +0,0 @@
import {Base64Decoder} from './base64-decoder';
describe('Base64Decoder', () => {
let sut: Base64Decoder;
beforeEach(() => sut = new Base64Decoder());
it('should create an instance', () => {
expect(sut).toBeTruthy();
});
it('should have a display name', () => {
expect(sut.getDisplayname()).toBeTruthy();
});
it('should have the id "base64decode"', () => {
expect(sut.getId()).toEqual('base64decode');
});
it('should decode "SGVsbG8sIFdvcmxkIQ==" to "Hello, World!"', () => {
expect(sut.convert('SGVsbG8sIFdvcmxkIQ==')).toEqual('Hello, World!');
});
it('should raise an exception on invalid input', () => {
expect(() => sut.convert('foo bar.')).toThrowError('Could not decode base64 string. Maybe corrupt input?');
});
});

View File

@ -1,27 +0,0 @@
import {Base64Encoder} from './base64-encoder';
describe('Base64Encoder', () => {
let sut: Base64Encoder;
beforeEach(() => sut = new Base64Encoder());
it('should create an instance', () => {
expect(sut).toBeTruthy();
});
it('should have a display name', () => {
expect(sut.getDisplayname()).toBeTruthy();
});
it('should have the id "base64encode"', () => {
expect(sut.getId()).toEqual('base64encode');
});
it('should encode "Oh, guete Tag!" to "T2gsIGd1ZXRlIFRhZyE="', () => {
expect(sut.convert('Oh, guete Tag!')).toEqual('T2gsIGd1ZXRlIFRhZyE=');
});
it('should raise an exception on invalid input', () => {
expect(() => sut.convert('€')).toThrowError(/Looks like you've got a character outside of the Latin1 range there./);
});
});

View File

@ -14,7 +14,7 @@ export class Base64Encoder implements Converter {
return btoa(input); return btoa(input);
} catch (exception) { } catch (exception) {
console.error(exception); console.error(exception);
throw new Error('Ouch! Looks like you\'ve got a character outside of the Latin1 range there. Too bad, this is not supported yet. ' throw new Error('Ouch! Looks like you\'ve got a UTF-8 character there. Too bad, this is not supported yet. '
+ 'We\'re working on it and hope to be ready soon! Why don\'t you ' + 'We\'re working on it and hope to be ready soon! Why don\'t you '
+ '<a href="https://duckduckgo.com/?q=cute+kitties&iar=images">enjoy some kittens</a> meanwhile?'); + '<a href="https://duckduckgo.com/?q=cute+kitties&iar=images">enjoy some kittens</a> meanwhile?');
} }

View File

@ -1,27 +0,0 @@
import {BinToDecConverter} from './bin-to-dec-converter';
describe('BinToDecConverter', () => {
let sut: BinToDecConverter;
beforeEach(() => sut = new BinToDecConverter());
it('should create an instance', () => {
expect(sut).toBeTruthy();
});
it('should have a display name', () => {
expect(sut.getDisplayname()).toBeTruthy();
});
it('should have the id "bintodec"', () => {
expect(sut.getId()).toEqual('bintodec');
});
it('should convert "11011" to "27"', () => {
expect(sut.convert('11011')).toEqual('27');
});
it('should raise an exception on invalid input', () => {
expect(() => sut.convert('1foo bar')).toThrowError('The input seems not to be a valid binary number.');
});
});

View File

@ -11,7 +11,7 @@ export class BinToDecConverter implements Converter {
convert(input: string): string { convert(input: string): string {
const n: number = parseInt(input, 2); const n: number = parseInt(input, 2);
if (isNaN(n) || !input.trim().match(/^([01]+)$/)) { if (isNaN(n)) {
throw new Error('The input seems not to be a valid binary number.'); throw new Error('The input seems not to be a valid binary number.');
} }
return n.toString(10); return n.toString(10);

View File

@ -1,27 +0,0 @@
import {DecToBinConverter} from './dec-to-bin-converter';
describe('DecToBinConverter', () => {
let sut: DecToBinConverter;
beforeEach(() => sut = new DecToBinConverter());
it('should create an instance', () => {
expect(sut).toBeTruthy();
});
it('should have a display name', () => {
expect(sut.getDisplayname()).toBeTruthy();
});
it('should have the id "dectobin"', () => {
expect(sut.getId()).toEqual('dectobin');
});
it('should convert "22" to "10110"', () => {
expect(sut.convert('22')).toEqual('10110');
});
it('should raise an exception on invalid input', () => {
expect(() => sut.convert('foo bar')).toThrowError('The input seems not to be a valid integer.');
});
});

View File

@ -1,27 +0,0 @@
import {DecToHexConverter} from './dec-to-hex-converter';
describe('DecToHexConverter', () => {
let sut: DecToHexConverter;
beforeEach(() => sut = new DecToHexConverter());
it('should create an instance', () => {
expect(sut).toBeTruthy();
});
it('should have a display name', () => {
expect(sut.getDisplayname()).toBeTruthy();
});
it('should have the id "dectohex"', () => {
expect(sut.getId()).toEqual('dectohex');
});
it('should convert "22" to "16"', () => {
expect(sut.convert('22')).toEqual('16');
});
it('should raise an exception on invalid input', () => {
expect(() => sut.convert('foo bar')).toThrowError('The input seems not to be a valid integer.');
});
});

View File

@ -1,27 +0,0 @@
import {HexToDecConverter} from './hex-to-dec-converter';
describe('HexToDecConverter', () => {
let sut: HexToDecConverter;
beforeEach(() => sut = new HexToDecConverter());
it('should create an instance', () => {
expect(sut).toBeTruthy();
});
it('should have a display name', () => {
expect(sut.getDisplayname()).toBeTruthy();
});
it('should have the id "hextodec"', () => {
expect(sut.getId()).toEqual('hextodec');
});
it('should convert "ab" to "171"', () => {
expect(sut.convert('ab')).toEqual('171');
});
it('should raise an exception on invalid input', () => {
expect(() => sut.convert('foo bar')).toThrowError('The input seems not to be a valid hexadecimal number.');
});
});

View File

@ -11,7 +11,7 @@ export class HexToDecConverter implements Converter {
convert(input: string): string { convert(input: string): string {
const n: number = parseInt(input, 16); const n: number = parseInt(input, 16);
if (isNaN(n) || !input.trim().match(/^((0x|0X)?[0-9a-fA-F]+)$/)) { if (isNaN(n)) {
throw new Error('The input seems not to be a valid hexadecimal number.'); throw new Error('The input seems not to be a valid hexadecimal number.');
} }
return n.toString(10); return n.toString(10);

View File

@ -1,24 +0,0 @@
import {HTMLEntitiesDecoder} from './htmlentities-decoder';
describe('HTMLEntitiesDecoder', () => {
let sut: HTMLEntitiesDecoder;
beforeEach(() => sut = new HTMLEntitiesDecoder());
it('should create an instance', () => {
expect(sut).toBeTruthy();
});
it('should have a display name', () => {
expect(sut.getDisplayname()).toBeTruthy();
});
it('should have the id "decodehtmlentities"', () => {
expect(sut.getId()).toEqual('decodehtmlentities');
});
it('should decode "&lt;span&gt;&quot;Hi&quot; &amp; &quot;Lo&quot;&lt;/span&gt;" to "<span>"Hi" & "Lo"</span>"', () => {
expect(sut.convert('&lt;span&gt;&quot;Hi&quot; &amp; &quot;Lo&quot;&lt;/span&gt;'))
.toEqual('<span>"Hi" & "Lo"</span>');
});
});

View File

@ -1,24 +0,0 @@
import {HTMLEntitiesEncoder} from './htmlentities-encoder';
describe('HTMLEntitiesEncoder', () => {
let sut: HTMLEntitiesEncoder;
beforeEach(() => sut = new HTMLEntitiesEncoder());
it('should create an instance', () => {
expect(sut).toBeTruthy();
});
it('should have a display name', () => {
expect(sut.getDisplayname()).toBeTruthy();
});
it('should have the id "encodehtmlentities"', () => {
expect(sut.getId()).toEqual('encodehtmlentities');
});
it('should encode "<span>"Hi" & "Lo"</span>" to "&lt;span&gt;&quot;Hi&quot; &amp; &quot;Lo&quot;&lt;/span&gt;"', () => {
expect(sut.convert('<span>"Hi" & "Lo"</span>'))
.toEqual('&lt;span&gt;&quot;Hi&quot; &amp; &quot;Lo&quot;&lt;/span&gt;');
});
});

View File

@ -11,7 +11,7 @@ export class HTMLEntitiesDecoder implements Converter {
convert(input: string): string { convert(input: string): string {
return input return input
.replace(/\&quot\;/g, '"') .replace(/\&quot\;/g, '\'')
.replace(/\&gt\;/g, '>') .replace(/\&gt\;/g, '>')
.replace(/\&lt\;/g, '<') .replace(/\&lt\;/g, '<')
.replace(/\&amp\;/g, '&'); .replace(/\&amp\;/g, '&');

View File

@ -14,6 +14,6 @@ export class HTMLEntitiesEncoder implements Converter {
.replace(/\&/g, '&amp;') .replace(/\&/g, '&amp;')
.replace(/\</g, '&lt;') .replace(/\</g, '&lt;')
.replace(/\>/g, '&gt;') .replace(/\>/g, '&gt;')
.replace(/\"/g, '&quot;'); .replace(/\'/g, '&quot;');
} }
} }

View File

@ -1,37 +0,0 @@
import {PunycodeDecoder} from './punycode-decoder';
import {NativeLibraryWrapperService, Punycode} from '../native-library-wrapper.service';
import createSpyObj = jasmine.createSpyObj;
import Spy = jasmine.Spy;
describe('PunycodeDecoder', () => {
let sut: PunycodeDecoder;
let nativeWrapperSpy: Partial<NativeLibraryWrapperService>;
let decodeSpy: Spy;
beforeEach(() => {
nativeWrapperSpy = {punycode: createSpyObj<Punycode>(['decode'])};
decodeSpy = nativeWrapperSpy.punycode!.decode as Spy;
sut = new PunycodeDecoder((nativeWrapperSpy as NativeLibraryWrapperService));
});
it('should create an instance', () => {
expect(sut).toBeTruthy();
});
it('should have a display name', () => {
expect(sut.getDisplayname()).toBeTruthy();
});
it('should have the id "decodepunycode"', () => {
expect(sut.getId()).toEqual('decodepunycode');
});
it('should call through to the native punycode decoder', () => {
const testInput = 'My input';
const expectedOutput = 'It worked';
decodeSpy.and.returnValue(expectedOutput);
const result: string = sut.convert(testInput);
expect(result).toEqual(expectedOutput);
expect(decodeSpy).toHaveBeenCalledWith(testInput);
});
});

View File

@ -1,37 +0,0 @@
import {NativeLibraryWrapperService, Punycode} from '../native-library-wrapper.service';
import {PunycodeEncoder} from './punycode-encoder';
import createSpyObj = jasmine.createSpyObj;
import Spy = jasmine.Spy;
describe('PunycodeEncoder', () => {
let sut: PunycodeEncoder;
let nativeWrapperSpy: Partial<NativeLibraryWrapperService>;
let encodeSpy: Spy;
beforeEach(() => {
nativeWrapperSpy = {punycode: createSpyObj<Punycode>(['encode'])};
encodeSpy = nativeWrapperSpy.punycode!.encode as Spy;
sut = new PunycodeEncoder(nativeWrapperSpy as NativeLibraryWrapperService);
});
it('should create an instance', () => {
expect(sut).toBeTruthy();
});
it('should have a display name', () => {
expect(sut.getDisplayname()).toBeTruthy();
});
it('should have the id "encodepunycode"', () => {
expect(sut.getId()).toEqual('encodepunycode');
});
it('should call through to the native punycode encoder', () => {
const testInput = 'My input';
const expectedOutput = 'It worked';
encodeSpy.and.returnValue(expectedOutput);
const result: string = sut.convert(testInput);
expect(result).toEqual(expectedOutput);
expect(encodeSpy).toHaveBeenCalledWith(testInput);
});
});

View File

@ -1,5 +1,5 @@
import {Converter} from './converter'; import {Converter} from './converter';
import {NativeLibraryWrapperService} from '../native-library-wrapper.service'; import {NativeLibraryWrapperService} from '../nativelibrarywrapper.service';
export class PunycodeDecoder implements Converter { export class PunycodeDecoder implements Converter {

View File

@ -1,5 +1,5 @@
import {Converter} from './converter'; import {Converter} from './converter';
import {NativeLibraryWrapperService} from '../native-library-wrapper.service'; import {NativeLibraryWrapperService} from '../nativelibrarywrapper.service';
export class PunycodeEncoder implements Converter { export class PunycodeEncoder implements Converter {

View File

@ -1,37 +0,0 @@
import {NativeLibraryWrapperService, QuotedPrintable} from '../native-library-wrapper.service';
import {QuotedPrintableDecoder} from './quoted-printable-decoder';
import createSpyObj = jasmine.createSpyObj;
import Spy = jasmine.Spy;
describe('QuotedPrintableDecoder', () => {
let sut: QuotedPrintableDecoder;
let nativeWrapperSpy: Partial<NativeLibraryWrapperService>;
let decodeSpy: Spy;
beforeEach(() => {
nativeWrapperSpy = {quotedPrintable: createSpyObj<QuotedPrintable>(['decode'])};
decodeSpy = nativeWrapperSpy.quotedPrintable!.decode as Spy;
sut = new QuotedPrintableDecoder((nativeWrapperSpy as NativeLibraryWrapperService));
});
it('should create an instance', () => {
expect(sut).toBeTruthy();
});
it('should have a display name', () => {
expect(sut.getDisplayname()).toBeTruthy();
});
it('should have the id "decodequotedprintable"', () => {
expect(sut.getId()).toEqual('decodequotedprintable');
});
it('should call through to the native quoted printable decoder', () => {
const testInput = 'My input';
const expectedOutput = 'It worked';
decodeSpy.and.returnValue(expectedOutput);
const result: string = sut.convert(testInput);
expect(result).toEqual(expectedOutput);
expect(decodeSpy).toHaveBeenCalledWith(testInput);
});
});

View File

@ -1,37 +0,0 @@
import {NativeLibraryWrapperService, QuotedPrintable} from '../native-library-wrapper.service';
import {QuotedPrintableEncoder} from './quoted-printable-encoder';
import createSpyObj = jasmine.createSpyObj;
import Spy = jasmine.Spy;
describe('QuotedPrintableEncoder', () => {
let sut: QuotedPrintableEncoder;
let nativeWrapperSpy: Partial<NativeLibraryWrapperService>;
let encodeSpy: Spy;
beforeEach(() => {
nativeWrapperSpy = {quotedPrintable: createSpyObj<QuotedPrintable>(['encode'])};
encodeSpy = nativeWrapperSpy.quotedPrintable!.encode as Spy;
sut = new QuotedPrintableEncoder(nativeWrapperSpy as NativeLibraryWrapperService);
});
it('should create an instance', () => {
expect(sut).toBeTruthy();
});
it('should have a display name', () => {
expect(sut.getDisplayname()).toBeTruthy();
});
it('should have the id "encodequotedprintable"', () => {
expect(sut.getId()).toEqual('encodequotedprintable');
});
it('should call through to the native quoted printable encoder', () => {
const testInput = 'My input';
const expectedOutput = 'It worked';
encodeSpy.and.returnValue(expectedOutput);
const result: string = sut.convert(testInput);
expect(result).toEqual(expectedOutput);
expect(encodeSpy).toHaveBeenCalledWith(testInput);
});
});

View File

@ -1,5 +1,5 @@
import {Converter} from './converter'; import {Converter} from './converter';
import {NativeLibraryWrapperService} from '../native-library-wrapper.service'; import {NativeLibraryWrapperService} from '../nativelibrarywrapper.service';
export class QuotedPrintableDecoder implements Converter { export class QuotedPrintableDecoder implements Converter {

View File

@ -1,5 +1,5 @@
import {Converter} from './converter'; import {Converter} from './converter';
import {NativeLibraryWrapperService} from '../native-library-wrapper.service'; import {NativeLibraryWrapperService} from '../nativelibrarywrapper.service';
export class QuotedPrintableEncoder implements Converter { export class QuotedPrintableEncoder implements Converter {

View File

@ -1,28 +0,0 @@
import {ROT13Converter} from './rot13-converter';
describe('ROT13Converter', () => {
let sut: ROT13Converter;
beforeEach(() => sut = new ROT13Converter());
it('should create an instance', () => {
expect(sut).toBeTruthy();
});
it('should have a display name', () => {
expect(sut.getDisplayname()).toBeTruthy();
});
it('should have the id "rot13"', () => {
expect(sut.getId()).toEqual('rot13');
});
it('should encode "Hello, World!" to "Uryyb, Jbeyq!"', () => {
expect(sut.convert('Hello, World!')).toEqual('Uryyb, Jbeyq!');
});
it('should return the original input after being applied twice', () => {
const input = 'Ok, so this string is just a bunch of letters. And numbers: 1, 2, 3. Ans others: /&%. Kthxbye!';
expect(sut.convert(sut.convert(input))).toEqual(input);
});
});

View File

@ -1,25 +0,0 @@
import {Converter} from './converter';
export class ROT13Converter implements Converter {
getDisplayname(): string {
return 'Perform ROT-13';
}
getId(): string {
return 'rot13';
}
convert(input: string): string {
try {
const inChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
const outChars = 'NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm';
const translate = (c: string) => {
const charIndex = inChars.indexOf(c);
return charIndex > -1 ? outChars[charIndex] : c;
};
return input.split('').map(translate).join('');
} catch (exception) {
throw new Error('Could not perform ROT-13 conversion. Maybe corrupt input?');
}
}
}

View File

@ -1,25 +0,0 @@
import {URIComponentDecoder} from './uricomponent-decoder';
describe('URIComponentDecoder', () => {
let sut: URIComponentDecoder;
beforeEach(() => sut = new URIComponentDecoder());
it('should create an instance', () => {
expect(sut).toBeTruthy();
});
it('should have a display name', () => {
expect(sut.getDisplayname()).toBeTruthy();
});
it('should have the id "uricomponentdecode"', () => {
expect(sut.getId()).toEqual('uricomponentdecode');
});
it('should decode "http%3A%2F%2Fm%C3%A4nu%3Agh%C3%ABim%40host%3Aport%2Fhi.there%3Foh%3Dwell%23ya" ' +
'to "http://mänu:ghëim@host:port/hi.there?oh=well#ya"', () => {
expect(sut.convert('http%3A%2F%2Fm%C3%A4nu%3Agh%C3%ABim%40host%3Aport%2Fhi.there%3Foh%3Dwell%23ya'))
.toEqual('http://mänu:ghëim@host:port/hi.there?oh=well#ya');
});
});

View File

@ -1,25 +0,0 @@
import {URIComponentEncoder} from './uricomponent-encoder';
describe('URIComponentEncoder', () => {
let sut: URIComponentEncoder;
beforeEach(() => sut = new URIComponentEncoder());
it('should create an instance', () => {
expect(sut).toBeTruthy();
});
it('should have a display name', () => {
expect(sut.getDisplayname()).toBeTruthy();
});
it('should have the id "uricomponentencode"', () => {
expect(sut.getId()).toEqual('uricomponentencode');
});
it('should encode "http://mänu:ghëim@host:port/hi.there?oh=well#ya" ' +
'to "http%3A%2F%2Fm%C3%A4nu%3Agh%C3%ABim%40host%3Aport%2Fhi.there%3Foh%3Dwell%23ya"', () => {
expect(sut.convert('http://mänu:ghëim@host:port/hi.there?oh=well#ya'))
.toEqual('http%3A%2F%2Fm%C3%A4nu%3Agh%C3%ABim%40host%3Aport%2Fhi.there%3Foh%3Dwell%23ya');
});
});

View File

@ -1,25 +0,0 @@
import {URIDecoder} from './uridecoder';
describe('URIDecoder', () => {
let sut: URIDecoder;
beforeEach(() => sut = new URIDecoder());
it('should create an instance', () => {
expect(sut).toBeTruthy();
});
it('should have a display name', () => {
expect(sut.getDisplayname()).toBeTruthy();
});
it('should have the id "uridecode"', () => {
expect(sut.getId()).toEqual('uridecode');
});
it('should decode "http://m%C3%A4nu:gh%C3%ABim@host:port/hi.there?oh=well#ya" ' +
'to "http://mänu:ghëim@host:port/hi.there?oh=well#ya"', () => {
expect(sut.convert('http://m%C3%A4nu:gh%C3%ABim@host:port/hi.there?oh=well#ya'))
.toEqual('http://mänu:ghëim@host:port/hi.there?oh=well#ya');
});
});

View File

@ -1,25 +0,0 @@
import {URIEncoder} from './uriencoder';
describe('URIEncoder', () => {
let sut: URIEncoder;
beforeEach(() => sut = new URIEncoder());
it('should create an instance', () => {
expect(sut).toBeTruthy();
});
it('should have a display name', () => {
expect(sut.getDisplayname()).toBeTruthy();
});
it('should have the id "uriencode"', () => {
expect(sut.getId()).toEqual('uriencode');
});
it('should encode "http://mänu:ghëim@host:port/hi.there?oh=well#ya" ' +
'to "http://m%C3%A4nu:gh%C3%ABim@host:port/hi.there?oh=well#ya"', () => {
expect(sut.convert('http://mänu:ghëim@host:port/hi.there?oh=well#ya'))
.toEqual('http://m%C3%A4nu:gh%C3%ABim@host:port/hi.there?oh=well#ya');
});
});

View File

@ -1,37 +0,0 @@
import {NativeLibraryWrapperService, Utf8} from '../native-library-wrapper.service';
import {UTF8Decoder} from './utf8-decoder';
import createSpyObj = jasmine.createSpyObj;
import Spy = jasmine.Spy;
describe('UTF8Decoder', () => {
let sut: UTF8Decoder;
let nativeWrapperSpy: Partial<NativeLibraryWrapperService>;
let decodeSpy: Spy;
beforeEach(() => {
nativeWrapperSpy = {utf8: createSpyObj<Utf8>(['decode'])};
decodeSpy = nativeWrapperSpy.utf8!.decode as Spy;
sut = new UTF8Decoder((nativeWrapperSpy as NativeLibraryWrapperService));
});
it('should create an instance', () => {
expect(sut).toBeTruthy();
});
it('should have a display name', () => {
expect(sut.getDisplayname()).toBeTruthy();
});
it('should have the id "decodeutf8"', () => {
expect(sut.getId()).toEqual('decodeutf8');
});
it('should call through to the native UTF-8 decoder', () => {
const testInput = 'My input';
const expectedOutput = 'It worked';
decodeSpy.and.returnValue(expectedOutput);
const result: string = sut.convert(testInput);
expect(result).toEqual(expectedOutput);
expect(decodeSpy).toHaveBeenCalledWith(testInput);
});
});

View File

@ -1,37 +0,0 @@
import {NativeLibraryWrapperService, Utf8} from '../native-library-wrapper.service';
import {UTF8Encoder} from './utf8-encoder';
import createSpyObj = jasmine.createSpyObj;
import Spy = jasmine.Spy;
describe('UTF8Encoder', () => {
let sut: UTF8Encoder;
let nativeWrapperSpy: Partial<NativeLibraryWrapperService>;
let encodeSpy: Spy;
beforeEach(() => {
nativeWrapperSpy = {utf8: createSpyObj<Utf8>(['encode'])};
encodeSpy = nativeWrapperSpy.utf8!.encode as Spy;
sut = new UTF8Encoder(nativeWrapperSpy as NativeLibraryWrapperService);
});
it('should create an instance', () => {
expect(sut).toBeTruthy();
});
it('should have a display name', () => {
expect(sut.getDisplayname()).toBeTruthy();
});
it('should have the id "encodeutf8"', () => {
expect(sut.getId()).toEqual('encodeutf8');
});
it('should call through to the native UTF-8 encoder', () => {
const testInput = 'My input';
const expectedOutput = 'It worked';
encodeSpy.and.returnValue(expectedOutput);
const result: string = sut.convert(testInput);
expect(result).toEqual(expectedOutput);
expect(encodeSpy).toHaveBeenCalledWith(testInput);
});
});

View File

@ -1,5 +1,5 @@
import {Converter} from './converter'; import {Converter} from './converter';
import {NativeLibraryWrapperService} from '../native-library-wrapper.service'; import {NativeLibraryWrapperService} from '../nativelibrarywrapper.service';
export class UTF8Decoder implements Converter { export class UTF8Decoder implements Converter {

View File

@ -1,5 +1,5 @@
import {Converter} from './converter'; import {Converter} from './converter';
import {NativeLibraryWrapperService} from '../native-library-wrapper.service'; import {NativeLibraryWrapperService} from '../nativelibrarywrapper.service';
export class UTF8Encoder implements Converter { export class UTF8Encoder implements Converter {

View File

@ -0,0 +1,73 @@
import {Injectable} from '@angular/core';
import {Converter} from './converter/converter';
import {Base64Encoder} from './converter/base64encoder';
import {Base64Decoder} from './converter/base64decoder';
import {URIEncoder} from './converter/uriencoder';
import {URIDecoder} from './converter/uridecoder';
import {URIComponentEncoder} from './converter/uricomponentencoder';
import {URIComponentDecoder} from './converter/uricomponentdecoder';
import {HTMLEntitiesEncoder} from './converter/htmlentitiesencoder';
import {HTMLEntitiesDecoder} from './converter/htmlentitiesdecoder';
import {DecToHexConverter} from './converter/dectohexconverter';
import {HexToDecConverter} from './converter/hextodecconverter';
import {DecToBinConverter} from './converter/dectobinconverter';
import {BinToDecConverter} from './converter/bintodecconverter';
import {QuotedPrintableDecoder} from './converter/quotedprintabledecoder';
import {QuotedPrintableEncoder} from './converter/quotedprintableencoder';
import {NativeLibraryWrapperService} from './nativelibrarywrapper.service';
import {PunycodeEncoder} from './converter/punycodeencoder';
import {PunycodeDecoder} from './converter/punycodedecoder';
import {UTF8Encoder} from './converter/utf8encoder';
import {UTF8Decoder} from './converter/utf8decoder';
@Injectable()
export class ConverterRegistryService {
private converters: Converter[] = [];
constructor(private wrapper: NativeLibraryWrapperService) {
this.init();
}
public getAllConverters(): Converter[] {
return this.converters;
}
public getConverter(id: string): Converter {
for (let i = 0; i < this.converters.length; i++) {
if (this.converters[i].getId() === id) {
return this.converters[i];
}
}
return undefined;
}
private init(): void {
this.registerConverter(new Base64Encoder());
this.registerConverter(new Base64Decoder());
this.registerConverter(new URIEncoder());
this.registerConverter(new URIDecoder());
this.registerConverter(new URIComponentEncoder());
this.registerConverter(new URIComponentDecoder());
this.registerConverter(new HTMLEntitiesEncoder());
this.registerConverter(new HTMLEntitiesDecoder());
this.registerConverter(new QuotedPrintableEncoder(this.wrapper));
this.registerConverter(new QuotedPrintableDecoder(this.wrapper));
this.registerConverter(new DecToHexConverter());
this.registerConverter(new HexToDecConverter());
this.registerConverter(new DecToBinConverter());
this.registerConverter(new BinToDecConverter());
this.registerConverter(new PunycodeEncoder(this.wrapper));
this.registerConverter(new PunycodeDecoder(this.wrapper));
this.registerConverter(new UTF8Encoder(this.wrapper));
this.registerConverter(new UTF8Decoder(this.wrapper));
}
private registerConverter(converter: Converter): void {
this.converters.forEach((c: Converter) => {
if (c.getId() === converter.getId()) {
throw new Error('Converter-ID ' + converter.getId() + ' is already registered!');
}
});
this.converters.push(converter);
}
}

View File

@ -1,3 +0,0 @@
@if (step.error) {
<div class="errormessage" [innerHTML]="step.message"></div>
}

View File

@ -1,4 +0,0 @@
.errormessage {
color: red;
text-align: center;
}

View File

@ -1,50 +0,0 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {ErrorMessageComponent} from './error-message.component';
import {Component, ViewChild} from '@angular/core';
import {Step} from '../step';
@Component({
template: '<app-error-message [step]="step"></app-error-message>'
})
class TestHostComponent {
public step: Step = new Step(42);
@ViewChild(ErrorMessageComponent)
public sutComponent!: ErrorMessageComponent;
}
describe('ErrorMessageComponent', () => {
let testHostComponent: TestHostComponent;
let testHostFixture: ComponentFixture<TestHostComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ErrorMessageComponent],
declarations: [TestHostComponent]
})
.compileComponents();
});
beforeEach(() => {
testHostFixture = TestBed.createComponent(TestHostComponent);
testHostComponent = testHostFixture.componentInstance;
testHostFixture.detectChanges();
});
it('should create', () => {
expect(testHostComponent.sutComponent).toBeTruthy();
});
it('should display an error message', () => {
// arrange
testHostComponent.step.error = true;
testHostComponent.step.message = 'This is an error message';
// act
testHostFixture.detectChanges();
// assert
expect(testHostComponent.sutComponent.step.error).toBeTruthy();
expect(testHostComponent.sutComponent.step.message).toEqual('This is an error message');
});
});

View File

@ -1,16 +0,0 @@
import {Component, Input} from '@angular/core';
import {Step} from '../step';
@Component({
selector: 'app-error-message',
templateUrl: './error-message.component.html',
standalone: true,
styleUrls: ['./error-message.component.scss']
})
export class ErrorMessageComponent {
@Input()
public step!: Step;
constructor() {
}
}

View File

@ -1,71 +0,0 @@
import {inject, TestBed} from '@angular/core/testing';
import {InputComponentManagerService} from './input-component-manager.service';
import {Step} from './step';
describe('InputComponentManagerService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [InputComponentManagerService]
});
});
it('should be created', inject([InputComponentManagerService], (service: InputComponentManagerService) => {
expect(service).toBeTruthy();
}));
it('should create a component if requesting the first one',
inject([InputComponentManagerService], (service: InputComponentManagerService) => {
const firstStep: Step = service.getFirst();
expect(firstStep).toBeTruthy();
expect(service.getFirst()).toBe(firstStep);
expect(service.getAllComponents().length).toEqual(1);
})
);
it('must not add a null Step', inject([InputComponentManagerService], (service: InputComponentManagerService) => {
expect(() => service.register(null!)).toThrowError();
expect(service.getAllComponents().length).toEqual(0);
}));
it('must not add an undefined Step', inject([InputComponentManagerService], (service: InputComponentManagerService) => {
expect(() => service.register(undefined!)).toThrowError();
expect(service.getAllComponents().length).toEqual(0);
}));
it('should register new Steps', inject([InputComponentManagerService], (service: InputComponentManagerService) => {
service.register(new Step(99));
expect(service.getAllComponents().length).toEqual(1);
service.register(new Step(100));
expect(service.getAllComponents().length).toEqual(2);
}));
it('should return a new step if the next step doesn\'t exist',
inject([InputComponentManagerService], (service: InputComponentManagerService) => {
// arrange
const firstStep: Step = new Step(0);
service.register(firstStep);
// act
const nextStep: Step = service.getNext(firstStep);
// assert
expect(service.getAllComponents().length).toEqual(2);
expect(nextStep.index).toEqual(1);
})
);
it('should return the next step if requested', inject([InputComponentManagerService], (service: InputComponentManagerService) => {
// arrange
const firstStep: Step = new Step(0);
const nextStep: Step = new Step(1);
service.register(firstStep);
service.register(nextStep);
// act
const requestedStep: Step = service.getNext(firstStep);
// assert
expect(service.getAllComponents().length).toEqual(2);
expect(requestedStep).toEqual(nextStep);
}));
});

View File

@ -1,9 +1,7 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {Step} from './step'; import {Step} from './step';
@Injectable({ @Injectable()
providedIn: 'root'
})
export class InputComponentManagerService { export class InputComponentManagerService {
private components: Step[] = []; private components: Step[] = [];
@ -11,9 +9,6 @@ export class InputComponentManagerService {
} }
public register(component: Step): void { public register(component: Step): void {
if (!component) {
throw new Error('component to add must not be empty or undefined');
}
this.components.push(component); this.components.push(component);
} }

View File

@ -1,33 +0,0 @@
import {inject, TestBed} from '@angular/core/testing';
import {NativeLibraryWrapperService} from './native-library-wrapper.service';
describe('NativeLibraryWrapperService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [NativeLibraryWrapperService]
});
});
it('should be created', inject([NativeLibraryWrapperService], (service: NativeLibraryWrapperService) => {
expect(service).toBeTruthy();
}));
it('should convert punycode', inject([NativeLibraryWrapperService], (service: NativeLibraryWrapperService) => {
expect(service.punycode).toBeTruthy();
expect(service.punycode.encode('bärneruhr')).toEqual('brneruhr-0za');
expect(service.punycode.decode('brneruhr-0za')).toEqual('bärneruhr');
}));
it('should convert utf8', inject([NativeLibraryWrapperService], (service: NativeLibraryWrapperService) => {
expect(service.utf8).toBeTruthy();
expect(service.utf8.encode('bärneruhr')).toEqual('bärneruhr');
expect(service.utf8.decode('bärneruhr')).toEqual('bärneruhr');
}));
it('should convert quoted printable', inject([NativeLibraryWrapperService], (service: NativeLibraryWrapperService) => {
expect(service.quotedPrintable).toBeTruthy();
expect(service.quotedPrintable.encode('bärneruhr')).toEqual('b=E4rneruhr');
expect(service.quotedPrintable.decode('b=E4rneruhr')).toEqual('bärneruhr');
}));
});

View File

@ -1,37 +0,0 @@
import {Injectable} from '@angular/core';
import * as NativePunycode from 'punycode/';
import * as NativeQuotedPrintable from 'quoted-printable';
import * as NativeUtf8 from 'utf8';
@Injectable({
providedIn: 'root'
})
export class NativeLibraryWrapperService {
public readonly utf8: Utf8;
public readonly quotedPrintable: QuotedPrintable;
public readonly punycode: Punycode;
constructor() {
this.utf8 = NativeUtf8;
this.quotedPrintable = NativeQuotedPrintable;
this.punycode = NativePunycode;
}
}
export interface Punycode {
encode(input: string): string;
decode(input: string): string;
}
export interface QuotedPrintable {
encode(input: string): string;
decode(input: string): string;
}
export interface Utf8 {
encode(input: any): string;
decode(input: string): any;
}

View File

@ -0,0 +1,20 @@
import {Injectable} from '@angular/core';
import {Punycode} from './punycode';
import {Utf8} from './utf8';
import {QuotedPrintable} from './quotedprintable';
import * as NativeUtf8 from 'utf8';
import * as NativeQuotedPrintable from 'quoted-printable';
import * as NativePunycode from 'punycode';
@Injectable()
export class NativeLibraryWrapperService {
public utf8: Utf8;
public quotedPrintable: QuotedPrintable;
public punycode: Punycode;
constructor() {
this.utf8 = NativeUtf8;
this.quotedPrintable = NativeQuotedPrintable;
this.punycode = NativePunycode;
}
}

4
src/app/punycode.ts Normal file
View File

@ -0,0 +1,4 @@
export interface Punycode {
encode(input: string): string;
decode(input: string): string;
}

View File

@ -0,0 +1,4 @@
export interface QuotedPrintable {
encode(input: string): string;
decode(input: string): string;
}

View File

@ -1,11 +1,11 @@
import {Converter} from './converter/converter'; import {Converter} from './converter/converter';
export class Step { export class Step {
public content: string = ''; public content = '';
public selectedConverter: Converter | undefined; public selectedConverter: Converter = undefined;
public index: number; public index: number;
public error: boolean = false; public error = false;
public message: string = ''; public message = '';
constructor(index: number) { constructor(index: number) {
this.index = index; this.index = index;

View File

@ -1,2 +0,0 @@
<textarea (keyup)="update(step)" placeholder="Please enter your input ..."
[(ngModel)]="step.content">{{ step.content }}</textarea>

View File

@ -1,65 +0,0 @@
:host {
border: 1px solid #888;
display: block;
margin: 0 0 1em 0;
padding: 0 1em 0 0;
position: relative;
transition: border ease-in-out 200ms;
&:after, &:before {
border: solid transparent;
content: " ";
height: 0;
left: 50%;
pointer-events: none;
position: absolute;
top: 100%;
transition: background-color ease-in-out 200ms, border ease-in-out 200ms;
width: 0;
}
&:after {
border-top-color: #222;
border-width: 1em;
margin-left: -1em;
}
&:before {
border-top-color: #888;
border-width: calc(1em + 1px);
margin-left: calc(-1em - 1px);
}
&:has(>textarea:focus, >textarea:hover) {
&:after {
border-top-color: #333;
}
&:before {
border-top-color: #bbb;
}
}
&:hover {
border-color: #bbb;
}
}
textarea {
background-color: #222;
border: none;
color: #ddd;
font-family: "Liberation Mono", monospace;
height: 10em;
margin: 0;
padding: 0.5em;
resize: vertical;
transition: background-color ease-in-out 200ms, border-color ease-in-out 200ms, color ease-in-out 200ms;
width: 100%;
&:focus, &:hover {
background-color: #333;
border-color: #bbb;
color: #fff;
}
}

View File

@ -1,109 +0,0 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {TextInputFieldComponent} from './text-input-field.component';
import {Step} from '../step';
import {Component, ViewChild} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {InputComponentManagerService} from '../input-component-manager.service';
import createSpyObj = jasmine.createSpyObj;
@Component({
template: '<app-text-input-field [step]="step"></app-text-input-field>'
})
class TestHostComponent {
public step: Step = new Step(42);
@ViewChild(TextInputFieldComponent)
public sutComponent!: TextInputFieldComponent;
}
describe('TextInputFieldComponent', () => {
let testHostComponent: TestHostComponent;
let testHostFixture: ComponentFixture<TestHostComponent>;
const inputComponentManagerServiceStub = createSpyObj(['getNext']);
inputComponentManagerServiceStub.getNext.and.returnValue(undefined);
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
TestHostComponent
],
imports: [TextInputFieldComponent, FormsModule],
providers: [{provide: InputComponentManagerService, useValue: inputComponentManagerServiceStub}]
})
.compileComponents();
});
beforeEach(() => {
testHostFixture = TestBed.createComponent(TestHostComponent);
testHostComponent = testHostFixture.componentInstance;
testHostFixture.detectChanges();
});
it('should create the component', () => {
expect(testHostComponent.sutComponent).toBeTruthy();
});
it('should not perform an update if no converter is selected', () => {
// arrange
const step: Step = testHostComponent.step;
step.content = 'Don\'t you change me!';
step.error = false;
step.message = 'There be no message.';
// act
testHostComponent.sutComponent.update(step);
// assert
expect(step.content).toEqual('Don\'t you change me!');
expect(step.error).toBeFalsy();
expect(step.message).toEqual('There be no message.');
});
it('should set the error flag if the conversion fails', () => {
// arrange
const step: Step = testHostComponent.step;
step.content = 'Don\'t you change me!';
step.error = false;
step.message = 'There be no message.';
step.selectedConverter = {
getId: () => 'testConverter',
convert: () => {
throw new Error('Test error');
},
getDisplayname: () => 'Testing converter'
};
// act
testHostComponent.sutComponent.update(step);
// assert
expect(step.content).toEqual('Don\'t you change me!');
expect(step.error).toBeTruthy();
expect(step.message).toEqual('Test error');
});
it('should correctly convert valid input', () => {
// arrange
const nextStep: Step = new Step(82);
inputComponentManagerServiceStub.getNext.and.returnValue(nextStep);
const step: Step = testHostComponent.step;
step.content = 'Don\'t you change me!';
step.error = true;
step.message = 'There be no message.';
step.selectedConverter = {
getId: () => 'testConverter',
convert: () => 'The Converted Result',
getDisplayname: () => 'Testing converter'
};
// act
testHostComponent.sutComponent.update(step);
// assert
expect(step.content).toEqual('Don\'t you change me!');
expect(step.error).toBeFalsy();
expect(step.message).toEqual('');
expect(nextStep.content).toEqual('The Converted Result');
});
});

View File

@ -1,48 +0,0 @@
import {Component, Input} from '@angular/core';
import {Step} from '../step';
import {Converter} from '../converter/converter';
import {InputComponentManagerService} from '../input-component-manager.service';
import {FormsModule} from '@angular/forms';
@Component({
selector: 'app-text-input-field',
templateUrl: './text-input-field.component.html',
standalone: true,
styleUrls: ['./text-input-field.component.scss'],
imports: [FormsModule]
})
export class TextInputFieldComponent {
@Input()
public step!: Step;
constructor(private inputComponentManagerService: InputComponentManagerService) {
}
update(step: Step): void {
const converter: Converter | undefined = step.selectedConverter;
if (converter !== undefined) {
const content: string = step.content;
let result: string | null;
try {
result = converter.convert(content);
} catch (error) {
if (typeof console === 'object' && typeof console.log === 'function') {
console.log(error);
}
step.message = (error as Error).message;
step.error = true;
result = null;
}
if (result !== null) {
step.message = '';
step.error = false;
if (result !== '') {
const nextComponent: Step = this.inputComponentManagerService.getNext(step);
nextComponent.content = result;
this.update(nextComponent);
}
}
}
}
}

4
src/app/utf8.ts Normal file
View File

@ -0,0 +1,4 @@
export interface Utf8 {
encode(input: any): string;
decode(input: string): any;
}

View File

@ -1 +0,0 @@
<div [ngClass]="{dev: !PROD}">Version: {{VERSION}}</div>

View File

@ -1,10 +0,0 @@
div {
font-size: smaller;
color: gray;
display: block;
text-align: right;
margin-right: 2em;
&.dev {
color: red;
}
}

View File

@ -1,36 +0,0 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {VersionComponent} from './version.component';
import packageInfo from '../../../package.json';
describe('VersionComponent', () => {
let component: VersionComponent;
let fixture: ComponentFixture<VersionComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [VersionComponent]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(VersionComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create the component', () => {
expect(component).toBeTruthy();
});
it('should contain the correct version', () => {
// when executing the test, we're always running with the dev environment
expect(component.VERSION).toEqual(`${packageInfo.version} (development build)`);
});
it('should have the correct value for the "production" property', () => {
// when executing the test, we're always running with the dev environment
expect(component.PROD).toBeFalsy();
});
});

View File

@ -1,18 +0,0 @@
import {Component, isDevMode} from '@angular/core';
import {CommonModule} from '@angular/common';
import packageInfo from '../../../package.json';
@Component({
selector: 'app-version',
templateUrl: './version.component.html',
standalone: true,
styleUrls: ['./version.component.scss'],
imports: [CommonModule]
})
export class VersionComponent {
public readonly PROD: boolean = !isDevMode();
public readonly VERSION: string = packageInfo.version + (this.PROD ? '' : ' (development build)');
constructor() {
}
}

Binary file not shown.

View File

@ -0,0 +1,636 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg>
<metadata>
Created by FontForge 20110222 at Fri Feb 17 12:20:04 2012
By www-data
Copyleft 2002, 2003, 2005, 2008, 2009, 2010 Free Software Foundation.
</metadata>
<defs>
<font id="FreeMono" horiz-adv-x="600">
<font-face
font-family="FreeMono"
font-weight="400"
font-stretch="normal"
units-per-em="1000"
panose-1="2 7 4 9 2 2 5 2 4 4"
ascent="800"
descent="-200"
x-height="417"
cap-height="563"
bbox="-445 -200 600 794"
underline-thickness="50"
underline-position="-100"
unicode-range="U+0020-2122"
/>
<missing-glyph/>
<glyph glyph-name="space" unicode=" "
/>
<glyph glyph-name="exclam" unicode="!"
d="M338 563l-18 -326q-2 -28 -21 -28q-18 0 -20 28l-18 326q0 2 -0.5 8t-0.5 8q0 17 11.5 28t28.5 11q16 0 27.5 -11t11.5 -28v-3t-0.5 -6t-0.5 -7zM293 85h13q23 0 38.5 -14.5t15.5 -35.5t-15.5 -35.5t-38.5 -14.5h-13q-22 0 -37.5 14.5t-15.5 35.5t15.5 35.5t37.5 14.5z
"/>
<glyph glyph-name="quotedbl" unicode="&#x22;"
d="M146 604h128l-34 -253q-4 -36 -30 -36t-30 36zM326 604h128l-34 -253q-4 -36 -30 -36t-30 36z"/>
<glyph glyph-name="numbersign" unicode="#"
d="M321 -36l16 225h-91l-15 -225q-3 -26 -21 -26q-8 0 -14 6t-6 15v8l16 222h-87q-27 0 -27 21q0 20 27 20h89l9 126h-78q-27 0 -27 21q0 20 27 20h81l16 225q2 25 21 25t19 -25v-3l-15 -222h91l16 225q1 25 20 25q20 0 20 -25v-3l-16 -222h89q27 0 27 -20q0 -21 -27 -21
h-91l-9 -126h80q27 0 27 -20q0 -21 -27 -21h-83l-16 -225q-1 -26 -21 -26q-7 0 -13.5 6t-6.5 12v8zM349 356h-91l-9 -126h91z"/>
<glyph glyph-name="dollar" unicode="$"
d="M446 201q0 36 -23 58t-57.5 29.5t-74.5 17t-74.5 20t-57.5 38t-23 71.5q0 54 40.5 94t103.5 47v52q0 27 21 27q20 0 20 -27v-52q33 -3 55 -12t49 -30q2 29 21 29q20 0 20 -27v-60q0 -27 -20 -27q-18 0 -21 22q-4 27 -40.5 47t-82.5 20q-52 0 -88.5 -30.5t-36.5 -73.5
q0 -33 23 -53.5t57.5 -27t74.5 -16t74.5 -20t57.5 -40.5t23 -76q0 -59 -45 -99t-121 -48v-119q0 -27 -20 -27q-21 0 -21 27v119q-35 2 -71 17t-55 37v-16q0 -27 -20 -27q-21 0 -21 27v79q0 27 21 27q17 0 20 -25q3 -32 46.5 -56t98.5 -24q64 0 105.5 30.5t41.5 77.5z"/>
<glyph glyph-name="percent" unicode="%"
d="M361 236q52 0 87.5 -36.5t35.5 -89.5q0 -50 -36 -86t-86 -36q-51 0 -87 36.5t-36 87.5t36 87.5t86 36.5zM361 198q-35 0 -59.5 -25t-24.5 -61q0 -35 25 -60.5t60 -25.5q34 0 59 25t25 60q0 37 -24.5 62t-60.5 25zM237 611q52 0 87.5 -36t35.5 -89q0 -50 -36 -86.5
t-86 -36.5q-51 0 -87 36.5t-36 87.5t36 87.5t86 36.5zM237 573q-34 0 -59 -25.5t-25 -60.5t25 -60.5t60 -25.5q34 0 59 25.5t25 59.5q0 37 -24.5 62t-60.5 25zM495 344l-379 -122q-8 -3 -12 -3q-6 0 -11.5 6t-5.5 13q0 14 18 20l379 122q8 2 12 2q7 0 12 -5.5t5 -13.5
q0 -13 -18 -19z"/>
<glyph glyph-name="ampersand" unicode="&#x26;"
d="M393 0l-31 48q-45 -64 -112 -64q-59 0 -102 47t-43 111q0 52 29.5 90.5t81.5 54.5q-34 55 -42 73t-8 40q0 49 36.5 84t86.5 35q33 0 66 -20q19 11 26 11t13 -6.5t6 -14.5q0 -12 -16 -21l-32 -16q-30 26 -63 26q-32 0 -57 -24.5t-25 -55.5t44 -100l111 -177q31 58 46 128
h43q27 0 27 -20q0 -21 -27 -21h-12q-22 -83 -53 -126l27 -41h38q27 0 27 -20q0 -21 -27 -21h-58zM338 86l-105 165q-40 -9 -63.5 -38.5t-23.5 -70.5q0 -47 31 -82t72 -35q55 0 89 61z"/>
<glyph glyph-name="quotesingle" unicode="'"
d="M236 604h128l-34 -253q-5 -36 -30 -36t-30 36z"/>
<glyph glyph-name="parenleft" unicode="("
d="M294 243q0 74 30.5 162.5t64.5 143.5t49 55q8 0 14 -6t6 -14q0 -11 -16 -40.5t-35.5 -65t-35.5 -100.5t-16 -138q0 -153 100 -333q3 -7 3 -11q0 -8 -6.5 -14t-14.5 -6q-16 0 -49.5 54.5t-63.5 144.5t-30 168z"/>
<glyph glyph-name="parenright" unicode=")"
d="M147 -104q0 11 16 40.5t35.5 65t35.5 100.5t16 138q0 154 -100 333q-3 7 -3 11q0 8 6.5 14t14.5 6q16 0 49.5 -54.5t63.5 -144.5t30 -168q0 -74 -30.5 -162.5t-64.5 -143.5t-49 -55q-8 0 -14 6t-6 14z"/>
<glyph glyph-name="asterisk" unicode="*"
d="M279 438v139q0 27 21 27q20 0 20 -27v-139l133 43q7 3 15 3q7 0 13 -6t6 -14q0 -15 -21 -22l-133 -42l81 -112q7 -12 7 -18q0 -8 -6 -14t-14 -6q-10 0 -20 14l-82 112l-81 -112q-11 -14 -21 -14q-8 0 -14 6t-6 15q0 6 8 17l81 112l-133 42q-20 5 -20 21q0 8 6 14t14 6
q3 0 13 -3z"/>
<glyph glyph-name="plus" unicode="+"
d="M320 261v-202q0 -27 -20 -27q-21 0 -21 27v202h-180q-27 0 -27 21q0 20 27 20h180v201q0 27 21 27q20 0 20 -27v-201h180q28 0 28 -20q0 -21 -28 -21h-180z"/>
<glyph glyph-name="comma" unicode=","
d="M207 145h133l-145 -265q-13 -25 -31 -25q-12 0 -20.5 8.5t-8.5 20.5q0 3 2 11z"/>
<glyph glyph-name="hyphen" unicode="-"
d="M501 258h-402q-27 0 -27 21q0 20 27 20h402q27 0 27 -20q0 -21 -27 -21z"/>
<glyph glyph-name="period" unicode="."
d="M295 116h10q29 0 49 -18.5t20 -46.5t-20 -47t-49 -19h-10q-29 0 -49 19t-20 47t20 46.5t49 18.5z"/>
<glyph glyph-name="slash" unicode="/"
d="M482 633l-327 -696q-9 -18 -22 -18q-8 0 -14 6t-6 13q0 5 5 16l327 696q8 18 22 18q8 0 14 -6t6 -13q0 -6 -5 -16z"/>
<glyph glyph-name="zero" unicode="0"
d="M487 351v-100q0 -118 -52 -192t-135 -74t-135 74t-52 192v100q0 119 52 193t135 74t135 -74t52 -193zM300 577q-68 0 -107 -68.5t-39 -162.5v-89q0 -98 40.5 -164.5t105.5 -66.5q68 0 107 68.5t39 162.5v89q0 98 -40.5 164.5t-105.5 66.5z"/>
<glyph glyph-name="one" unicode="1"
d="M321 612v-571h139q27 0 27 -20q0 -21 -27 -21h-319q-28 0 -28 21q0 20 28 20h139v517l-113 -113q-8 -8 -21 -8q-8 0 -13.5 6.5t-5.5 16.5q0 9 11 20l132 132h51z"/>
<glyph glyph-name="two" unicode="2"
d="M104 470q0 45 54.5 96.5t131.5 51.5q73 0 128.5 -52t55.5 -120q0 -45 -26.5 -84.5t-117.5 -124.5l-207 -193v-3h314v36q0 27 21 27q20 0 20 -27v-77h-394v60l236 222q70 69 91.5 100t21.5 65q0 52 -43 91t-100 39q-51 0 -92.5 -30t-53.5 -75q-6 -20 -21 -20q-7 0 -13 5.5
t-6 12.5z"/>
<glyph glyph-name="three" unicode="3"
d="M125 528q0 11 19.5 31t62.5 39.5t94 19.5q75 0 125.5 -44.5t50.5 -109.5q0 -58 -32.5 -92t-70.5 -44q59 -24 92 -66.5t33 -93.5q0 -76 -61.5 -129.5t-147.5 -53.5q-59 0 -126.5 29.5t-67.5 54.5q0 7 6 13t13 6t28 -15.5t60 -31t88 -15.5q68 0 117.5 42t49.5 100
q0 59 -53 101.5t-126 42.5q-27 0 -27 21q0 20 27 20q157 0 157 110q0 49 -39 81.5t-97 32.5q-42 0 -71.5 -10.5t-41 -23.5t-22.5 -23.5t-20 -10.5q-8 0 -14 5.5t-6 13.5z"/>
<glyph glyph-name="four" unicode="4"
d="M376 169h-271v47l228 388h84v-394h34q27 0 27 -21q0 -20 -27 -20h-34v-128h34q27 0 27 -20q0 -21 -27 -21h-151q-27 0 -27 21q0 20 27 20h76v128zM376 210v353h-24l-208 -353h232z"/>
<glyph glyph-name="five" unicode="5"
d="M316 354q-39 0 -70 -10t-50 -20.5t-28 -10.5q-19 0 -19 22v269h282q28 0 28 -20q0 -21 -28 -21h-241v-198q73 30 132 30q77 0 127 -55t50 -139q0 -95 -57.5 -155.5t-146.5 -60.5q-39 0 -77.5 12t-64 28.5t-41.5 32.5t-16 24t6 14t14 6q7 0 28 -19t60 -38t89 -19
q73 0 119 49t46 128q0 67 -39.5 109t-102.5 42z"/>
<glyph glyph-name="six" unicode="6"
d="M488 563q-3 0 -19.5 7t-44.5 7q-40 0 -82 -18.5t-80 -52.5t-62.5 -92t-24.5 -129q0 -5 2 -43q61 122 166 122q68 0 117.5 -55t49.5 -131q0 -81 -50 -137t-123 -56q-89 0 -145 83t-56 214q0 149 84.5 242.5t207.5 93.5q32 0 55.5 -10.5t23.5 -24.5q0 -8 -5.5 -14t-13.5 -6
zM183 188q21 -81 58 -121.5t97 -40.5q56 0 93.5 44t37.5 108q0 59 -37.5 102t-89.5 43q-25 0 -49.5 -12t-39.5 -24.5t-32.5 -37t-22 -32.5t-15.5 -29z"/>
<glyph glyph-name="seven" unicode="7"
d="M437 545v18h-291v-35q0 -28 -20 -28q-21 0 -21 28v76h373v-65l-163 -519q-7 -21 -20 -21q-8 0 -14 6t-6 14q0 5 2 13z"/>
<glyph glyph-name="eight" unicode="8"
d="M375 313q112 -51 112 -152q0 -73 -55 -124.5t-132 -51.5t-132 51.5t-55 124.5q0 100 112 152q-102 52 -102 140q0 67 52.5 116t124.5 49t124.5 -49t52.5 -116q0 -88 -102 -140zM300 577q-57 0 -96.5 -36.5t-39.5 -89.5q0 -49 39.5 -83.5t96.5 -34.5q56 0 96 34t40 83
q0 53 -39.5 90t-96.5 37zM300 293q-61 0 -103.5 -38t-42.5 -94t42.5 -95.5t103.5 -39.5q60 0 103 39.5t43 94.5q0 57 -42 95t-104 38z"/>
<glyph glyph-name="nine" unicode="9"
d="M158 40q3 0 19.5 -7t44.5 -7q40 0 82 18.5t80 52.5t62.5 92t24.5 129q0 5 -2 43q-61 -122 -166 -122q-68 0 -117.5 55t-49.5 131q0 81 50 137t123 56q89 0 145 -83t56 -214q0 -149 -84.5 -242.5t-207.5 -93.5q-32 0 -55.5 10.5t-23.5 24.5q0 8 5.5 14t13.5 6zM463 415
q-21 81 -58 121.5t-97 40.5q-56 0 -93.5 -44t-37.5 -108q0 -59 37.5 -102t89.5 -43q25 0 49.5 12t39.5 24.5t32.5 37t22 32.5t15.5 29z"/>
<glyph glyph-name="colon" unicode=":"
d="M295 116h10q29 0 49 -18.5t20 -46.5t-20 -47t-49 -19h-10q-29 0 -49 19t-20 47t20 46.5t49 18.5zM295 417h10q29 0 49 -19t20 -47t-20 -47t-49 -19h-10q-29 0 -49 19t-20 47t20 47t49 19z"/>
<glyph glyph-name="semicolon" unicode=";"
d="M211 145h133l-145 -265q-13 -25 -31 -25q-12 0 -20.5 8.5t-8.5 20.5q0 3 2 11zM271 417h10q29 0 49 -19t20 -47t-20 -47t-49 -19h-10q-29 0 -49 19t-20 47t20 47t49 19z"/>
<glyph glyph-name="less" unicode="&#x3c;"
d="M522 67q0 -7 -6 -15t-14 -8q-5 0 -13 5l-417 232l418 232q8 5 12 5q7 0 13.5 -7t6.5 -15q0 -11 -13 -18l-355 -197l355 -197q13 -7 13 -17z"/>
<glyph glyph-name="equal" unicode="="
d="M522 334h-444q-27 0 -27 21q0 20 27 20h444q27 0 27 -20q0 -21 -27 -21zM522 190h-444q-27 0 -27 21q0 20 27 20h444q27 0 27 -20q0 -21 -27 -21z"/>
<glyph glyph-name="greater" unicode="&#x3e;"
d="M78 496q0 5 5.5 13.5t14.5 8.5q5 0 13 -5l417 -232l-418 -232q-8 -5 -12 -5q-8 0 -14 7t-6 16q0 10 13 17l355 197l-355 197q-13 7 -13 18z"/>
<glyph glyph-name="question" unicode="?"
d="M134 529q10 3 41 17.5t62.5 22.5t71.5 8q80 0 129 -42.5t49 -111.5q0 -56 -35.5 -94t-129.5 -82v-41q0 -27 -20 -27q-21 0 -21 27v68q88 37 126.5 71.5t38.5 77.5q0 50 -40 81.5t-102 31.5q-61 0 -129 -34v-41q0 -27 -20 -27q-21 0 -21 27v68zM287 85h27q26 0 41.5 -14
t15.5 -36t-16 -36t-41 -14h-27q-26 0 -41.5 14t-15.5 36t15.5 36t41.5 14z"/>
<glyph glyph-name="at" unicode="@"
d="M299 -62q-87 0 -140.5 74.5t-53.5 185.5v163q0 114 55 188.5t140 74.5q68 0 113.5 -46t45.5 -115v-277q19 -3 19 -20q0 -21 -27 -21h-33v5q-30 -3 -40 -3q-58 0 -97 36t-39 89q0 61 48.5 102.5t127.5 41.5v47q0 52 -33 86t-84 34q-68 0 -111.5 -63t-43.5 -162v-160
q0 -93 43 -156t116 -63q42 0 68.5 10.5t38 20.5t17.5 10q8 0 14 -5.5t6 -13.5q0 -23 -47.5 -43t-102.5 -20zM418 192v184q-59 0 -97 -29t-38 -74q0 -38 27.5 -61.5t70.5 -23.5q21 0 37 4z"/>
<glyph glyph-name="A" unicode="A"
d="M428 188h-266l-54 -147h79q27 0 27 -20q0 -21 -27 -21h-151q-27 0 -27 21q0 20 27 20h31l179 481h-120q-27 0 -27 21q0 20 27 20h204l197 -522h37q27 0 27 -20q0 -21 -27 -21h-156q-27 0 -27 21q0 20 27 20h76zM413 229l-112 293h-15l-108 -293h235z"/>
<glyph glyph-name="B" unicode="B"
d="M124 41v481h-54q-27 0 -27 21q0 20 27 20h258q73 0 122 -41.5t49 -103.5q0 -74 -85 -120q127 -45 127 -145q0 -63 -48.5 -108t-116.5 -45h-306q-27 0 -27 21q0 20 27 20h54zM165 313h147q64 0 105 30t41 76q0 44 -37 73.5t-92 29.5h-164v-209zM500 153q0 21 -8.5 40
t-27.5 37.5t-57.5 30t-92.5 11.5h-149v-231h208q53 0 90 32.5t37 79.5z"/>
<glyph glyph-name="C" unicode="C"
d="M63 325q0 19 5.5 46.5t22.5 64.5t42 67t70 51.5t102 21.5q101 0 173 -69v29q0 27 21 27q20 0 20 -27v-112q0 -27 -21 -27q-18 0 -20 24q-3 46 -54.5 80t-118.5 34q-83 0 -142 -63.5t-59 -152.5v-71q0 -90 65 -156.5t153 -66.5q52 0 93 21t81 69q9 10 18 10q20 0 20 -19
q0 -9 -16 -28t-42 -40.5t-68 -37.5t-86 -16q-102 0 -180.5 78.5t-78.5 179.5v83z"/>
<glyph glyph-name="D" unicode="D"
d="M104 41v481h-34q-27 0 -27 21q0 20 27 20h220q97 0 163.5 -73.5t66.5 -179.5v-56q0 -107 -66.5 -180.5t-163.5 -73.5h-220q-27 0 -27 21q0 20 27 20h34zM479 318q0 20 -9 50t-28.5 66.5t-60 62t-93.5 25.5h-143v-481h150q72 0 128 62t56 142v73z"/>
<glyph glyph-name="E" unicode="E"
d="M165 272v-231h314v119q0 27 21 27q20 0 20 -27v-160h-450q-27 0 -27 21q0 20 27 20h54v481h-54q-27 0 -27 21q0 20 27 20h429v-139q0 -27 -21 -27q-20 0 -20 27v98h-293v-209h145v45q0 27 21 27q20 0 20 -27v-131q0 -27 -20 -27q-21 0 -21 27v45h-145z"/>
<glyph glyph-name="F" unicode="F"
d="M165 272v-231h138q28 0 28 -20q0 -21 -28 -21h-233q-27 0 -27 21q0 20 27 20h54v481h-54q-27 0 -27 21q0 20 27 20h450v-139q0 -27 -21 -27q-20 0 -20 27v98h-314v-209h145v45q0 27 20 27q21 0 21 -27v-131q0 -27 -21 -27q-20 0 -20 27v45h-145z"/>
<glyph glyph-name="G" unicode="G"
d="M315 535q-62 0 -107 -27t-65.5 -66t-29.5 -71t-9 -54v-73q0 -102 62 -160.5t170 -58.5q70 0 143 34v150h-139q-27 0 -27 21q0 20 27 20h194q28 0 28 -20q0 -21 -28 -21h-14v-173q-90 -52 -187 -52q-123 0 -196.5 71t-73.5 189v74q0 105 70.5 181.5t179.5 76.5
q99 0 166 -54v14q0 27 21 27q20 0 20 -27v-91q0 -27 -20 -27q-19 0 -20 25q-3 38 -51 65t-114 27z"/>
<glyph glyph-name="H" unicode="H"
d="M437 272h-270v-231h54q27 0 27 -20q0 -21 -27 -21h-140q-28 0 -28 21q0 20 28 20h45v481h-25q-27 0 -27 21q0 20 27 20h120q27 0 27 -20q0 -21 -27 -21h-54v-209h270v209h-54q-27 0 -27 21q0 20 27 20h120q27 0 27 -20q0 -21 -27 -21h-25v-481h46q27 0 27 -20
q0 -21 -27 -21h-141q-27 0 -27 21q0 20 27 20h54v231z"/>
<glyph glyph-name="I" unicode="I"
d="M320 522v-481h139q28 0 28 -20q0 -21 -28 -21h-319q-27 0 -27 21q0 20 27 20h139v481h-139q-27 0 -27 21q0 20 27 20h319q28 0 28 -20q0 -21 -28 -21h-139z"/>
<glyph glyph-name="J" unicode="J"
d="M460 522v-357q0 -74 -55.5 -127.5t-132.5 -53.5q-48 0 -91 20.5t-97 69.5v149q0 27 21 27q20 0 20 -27v-130q75 -68 148 -68q61 0 103.5 41t42.5 99v357h-159q-27 0 -27 21q0 20 27 20h296q27 0 27 -20q0 -21 -27 -21h-96z"/>
<glyph glyph-name="K" unicode="K"
d="M165 221v-180h75q27 0 27 -20q0 -21 -27 -21h-170q-27 0 -27 21q0 20 27 20h54v481h-54q-27 0 -27 21q0 20 27 20h170q27 0 27 -20q0 -21 -27 -21h-75v-249l280 249h-45q-28 0 -28 21q0 20 28 20h118q28 0 28 -20q0 -21 -28 -21h-16l-222 -198q73 -30 113 -84.5
t96 -198.5h56q27 0 27 -20q0 -21 -27 -21h-87q-55 152 -98 211.5t-114 82.5z"/>
<glyph glyph-name="L" unicode="L"
d="M227 522v-481h273v160q0 27 21 27q20 0 20 -27v-201h-451q-27 0 -27 21q0 20 27 20h96v481h-96q-27 0 -27 21q0 20 27 20h233q27 0 27 -20q0 -21 -27 -21h-96z"/>
<glyph glyph-name="M" unicode="M"
d="M326 169h-46l-159 353h-8v-481h74q28 0 28 -20q0 -21 -28 -21h-149q-27 0 -27 21q0 20 27 20h34v481h-25q-27 0 -27 21q0 20 27 20h99l157 -348l154 348h100q27 0 27 -20q0 -21 -27 -21h-25v-481h34q27 0 27 -20q0 -21 -27 -21h-149q-27 0 -27 21q0 20 27 20h74v481h-8z
"/>
<glyph glyph-name="N" unicode="N"
d="M501 0h-52l-305 504v-463h75q27 0 27 -20q0 -21 -27 -21h-150q-27 0 -27 21q0 20 27 20h34v481h-54q-27 0 -27 21q0 20 27 20h106l305 -504v463h-74q-28 0 -28 21q0 20 28 20h149q27 0 27 -20q0 -21 -27 -21h-34v-522z"/>
<glyph glyph-name="O" unicode="O"
d="M300 576q106 0 177.5 -86.5t71.5 -213.5q0 -121 -73 -206.5t-176 -85.5q-104 0 -176.5 86t-72.5 210t72.5 210t176.5 86zM300 535q-86 0 -147 -75t-61 -180t61 -180t147 -75q85 0 146.5 74.5t61.5 177.5q0 108 -60.5 183t-147.5 75z"/>
<glyph glyph-name="P" unicode="P"
d="M165 231v-190h138q28 0 28 -20q0 -21 -28 -21h-233q-27 0 -27 21q0 20 27 20h54v481h-54q-27 0 -27 21q0 20 27 20h240q80 0 134.5 -47.5t54.5 -117.5t-59 -118.5t-144 -48.5h-131zM165 272h134q65 0 112 37t47 89q0 51 -42.5 87.5t-101.5 36.5h-149v-250z"/>
<glyph glyph-name="Q" unicode="Q"
d="M318 -89q-48 0 -95.5 -12.5t-54.5 -12.5q-8 0 -14 6t-6 15q0 10 11 18l90 65q-85 19 -141.5 101.5t-56.5 188.5q0 124 72.5 210t176.5 86t176.5 -86t72.5 -210q0 -123 -70 -208t-174 -88l-54 -40q40 8 67 8q42 0 77.5 -13t49.5 -13q24 0 50.5 15.5t30.5 15.5q8 0 14 -6
t6 -15q0 -15 -36.5 -33t-67.5 -18q-21 0 -55.5 13t-68.5 13zM300 535q-86 0 -147 -75t-61 -180t61 -180t147 -75q85 0 146.5 74.5t61.5 177.5q0 108 -60.5 183t-147.5 75z"/>
<glyph glyph-name="R" unicode="R"
d="M165 251v-210h75q27 0 27 -20q0 -21 -27 -21h-170q-27 0 -27 21q0 20 27 20h54v481h-54q-27 0 -27 21q0 20 27 20h248q73 0 126.5 -46.5t53.5 -109.5q0 -97 -139 -145q47 -32 79 -73t93 -148h31q27 0 27 -20q0 -21 -27 -21h-57q-66 122 -108.5 173.5t-99.5 77.5h-132z
M165 292h114q74 0 126 34t52 82q0 45 -42.5 79.5t-96.5 34.5h-153v-230z"/>
<glyph glyph-name="S" unicode="S"
d="M464 153q0 42 -26 68t-64.5 34.5t-84 19t-84 22t-64.5 43t-26 81.5q0 66 52.5 110.5t131.5 44.5q86 0 146 -59v19q0 27 21 27q20 0 20 -27v-103q0 -27 -20 -27t-21 24q-3 45 -44 75t-99 30q-62 0 -102.5 -33t-40.5 -82q0 -39 26 -63t64.5 -32.5t84 -20t84 -23.5
t64.5 -45.5t26 -84.5q0 -73 -58 -120t-148 -47q-106 0 -169 72v-29q0 -27 -21 -27q-20 0 -20 27v112q0 27 21 27q19 0 20 -24q2 -49 51 -83t117 -34q70 0 116.5 36.5t46.5 91.5z"/>
<glyph glyph-name="T" unicode="T"
d="M321 41h105q27 0 27 -20q0 -21 -27 -21h-251q-27 0 -27 21q0 20 27 20h105v481h-167v-73q0 -27 -21 -27q-20 0 -20 27v114h456v-114q0 -27 -20 -27q-9 0 -15 7.5t-6 19.5v73h-166v-481z"/>
<glyph glyph-name="U" unicode="U"
d="M499 522v-337q0 -85 -57.5 -143t-141.5 -58q-85 0 -142 57.5t-57 143.5v337h-34q-27 0 -27 21q0 20 27 20h149q27 0 27 -20q0 -21 -27 -21h-74v-337q0 -68 45.5 -114t112.5 -46q66 0 112 46.5t46 113.5v337h-74q-27 0 -27 21q0 20 27 20h149q27 0 27 -20q0 -21 -27 -21
h-34z"/>
<glyph glyph-name="V" unicode="V"
d="M267 0l-200 522h-31q-27 0 -27 21q0 20 27 20h151q27 0 27 -20q0 -21 -27 -21h-77l186 -481h3l193 481h-78q-27 0 -27 21q0 20 27 20h150q27 0 27 -20q0 -21 -27 -21h-31l-209 -522h-57z"/>
<glyph glyph-name="W" unicode="W"
d="M122 0l-60 522h-15q-27 0 -27 21q0 20 27 20h149q28 0 28 -20q0 -21 -28 -21h-93l56 -476l112 392h62l109 -392l57 476h-96q-27 0 -27 21q0 20 27 20h150q27 0 27 -20q0 -21 -27 -21h-15l-62 -522h-64l-112 400l-115 -400h-63z"/>
<glyph glyph-name="X" unicode="X"
d="M325 288l191 -247h17q27 0 27 -20q0 -21 -27 -21h-132q-27 0 -27 21q0 20 27 20h62l-165 214l-164 -214h64q27 0 27 -20q0 -21 -27 -21h-131q-27 0 -27 21q0 20 27 20h17l188 247l-179 234h-15q-27 0 -27 21q0 20 27 20h110q27 0 27 -20q0 -21 -27 -21h-43l155 -201
l153 201h-45q-27 0 -27 21q0 20 27 20h111q27 0 27 -20q0 -21 -27 -21h-15z"/>
<glyph glyph-name="Y" unicode="Y"
d="M322 254v-213h105q27 0 27 -20q0 -21 -27 -21h-251q-27 0 -27 21q0 20 27 20h105v213l-179 268h-24q-27 0 -27 21q0 20 27 20h111q27 0 27 -20q0 -21 -27 -21h-38l152 -227l149 227h-40q-28 0 -28 21q0 20 28 20h110q27 0 27 -20q0 -21 -27 -21h-24z"/>
<glyph glyph-name="Z" unicode="Z"
d="M497 0h-394v59l328 460v3h-269v-118q0 -28 -20 -28q-21 0 -21 28v159h349v-58l-328 -460v-4h314v141q0 27 20 27q21 0 21 -27v-182z"/>
<glyph glyph-name="bracketleft" unicode="["
d="M321 563v-646h97q27 0 27 -20q0 -21 -27 -21h-138v728h138q27 0 27 -20q0 -21 -27 -21h-97z"/>
<glyph glyph-name="backslash" unicode="\"
d="M155 650l327 -696q5 -9 5 -16t-6 -13t-14 -6q-13 0 -22 18l-327 696q-5 10 -5 16q0 7 6.5 13t14.5 6q12 0 21 -18z"/>
<glyph glyph-name="bracketright" unicode="]"
d="M279 -83v646h-97q-27 0 -27 20q0 21 27 21h138v-728h-138q-27 0 -27 20q0 21 27 21h97z"/>
<glyph glyph-name="asciicircum" unicode="^"
d="M300 615l178 -223q9 -10 9 -18t-6 -14t-14 -6q-10 0 -20 13l-147 185l-147 -185q-9 -13 -20 -13q-8 0 -14 6t-6 14t9 18z"/>
<glyph glyph-name="underscore" unicode="_"
d="M600 -75v-50h-600v50h600z"/>
<glyph glyph-name="grave" unicode="`"
d="M194 630l114 -100q12 -11 12 -19q0 -9 -6 -15t-15 -6q-7 0 -18 9l-114 100q-12 11 -12 20q0 8 6 14t14 6q7 0 19 -9z"/>
<glyph glyph-name="a" unicode="a"
d="M125 378q0 22 68 37.5t106 15.5q70 0 115.5 -35t45.5 -88v-267h54q27 0 27 -20q0 -21 -27 -21h-95v67q-89 -83 -191 -83q-70 0 -113 35.5t-43 92.5q0 65 58.5 105.5t152.5 40.5q59 0 136 -21v71q0 37 -34 59.5t-89 22.5q-46 0 -95 -16t-57 -16t-13.5 6t-5.5 14zM419 112
v90q-60 15 -128 15q-79 0 -128.5 -29.5t-49.5 -76.5q0 -39 31 -62.5t83 -23.5q53 0 97.5 20t94.5 67z"/>
<glyph glyph-name="b" unicode="b"
d="M144 604v-276q73 103 180 103q91 0 154 -64t63 -157q0 -94 -63.5 -160t-153.5 -66q-110 0 -180 104v-88h-95q-27 0 -27 21q0 20 27 20h54v522h-54q-27 0 -27 21q0 20 27 20h95zM322 390q-74 0 -126 -53t-52 -129t52 -129.5t126 -53.5q73 0 125.5 53t52.5 127
q0 78 -51.5 131.5t-126.5 53.5z"/>
<glyph glyph-name="c" unicode="c"
d="M535 88q0 -9 -17 -25.5t-44.5 -34.5t-72.5 -31t-92 -13q-98 0 -161.5 62t-63.5 158q0 99 65 163t165 64q93 0 156 -55v13q0 28 21 28q20 0 20 -28v-91q0 -27 -20 -27q-18 0 -21 24q-3 40 -49.5 67.5t-109.5 27.5q-83 0 -134.5 -51.5t-51.5 -133.5q0 -79 52 -129.5
t134 -50.5q109 0 187 72q10 10 18 10t13.5 -5.5t5.5 -13.5z"/>
<glyph glyph-name="d" unicode="d"
d="M502 604v-563h54q27 0 27 -20q0 -21 -27 -21h-95v89q-71 -105 -182 -105q-89 0 -152.5 66t-63.5 158t63.5 157.5t152.5 65.5q110 0 182 -104v236h-54q-27 0 -27 21q0 20 27 20h95zM282 390q-74 0 -126 -53t-52 -129q0 -77 52 -130t127 -53q74 0 126 53t52 128
q0 78 -51.5 131t-127.5 53z"/>
<glyph glyph-name="e" unicode="e"
d="M520 199h-416q11 -79 66 -126.5t136 -47.5q47 0 96 15t79 39q10 7 16 7q8 0 13.5 -6t5.5 -14q0 -26 -71.5 -54t-139.5 -28q-101 0 -171.5 68t-70.5 165q0 91 66 152.5t162 61.5q101 0 165 -63.5t64 -168.5zM104 240h374q-12 68 -63 109t-124 41t-123 -40.5t-64 -109.5z
"/>
<glyph glyph-name="f" unicode="f"
d="M520 551q-2 0 -25 3t-55.5 6t-57.5 3q-49 0 -80 -23.5t-31 -61.5v-61h189q27 0 27 -20q0 -21 -27 -21h-189v-335h178q27 0 27 -20q0 -21 -27 -21h-317q-27 0 -27 21q0 20 27 20h98v335h-88q-27 0 -27 21q0 20 27 20h88v61q0 55 43.5 90.5t110.5 35.5q70 0 139 -13
q18 -5 18 -20q0 -20 -21 -20z"/>
<glyph glyph-name="g" unicode="g"
d="M440 334v83h95q27 0 27 -20q0 -21 -27 -21h-54v-404q0 -64 -48 -111t-117 -47h-114q-27 0 -27 21q0 20 27 20h116q51 0 86.5 36t35.5 87v129q-66 -97 -171 -97q-85 0 -145.5 62t-60.5 149t60.5 148.5t145.5 61.5q106 0 171 -97zM272 390q-70 0 -119 -49t-49 -120
t49 -120.5t119 -49.5t119 49t49 119q0 73 -48.5 122t-119.5 49z"/>
<glyph glyph-name="h" unicode="h"
d="M437 288q0 45 -34 73.5t-89 28.5q-42 0 -69.5 -16.5t-68.5 -64.5l-11 -13v-255h45q28 0 28 -20q0 -21 -28 -21h-132q-27 0 -27 21q0 20 27 20h46v522h-54q-27 0 -27 21q0 20 27 20h95v-257q38 46 73 65t81 19q70 0 114.5 -39.5t44.5 -100.5v-250h45q28 0 28 -20
q0 -21 -28 -21h-131q-27 0 -27 21q0 20 27 20h45v247z"/>
<glyph glyph-name="i" unicode="i"
d="M320 417v-376h160q28 0 28 -20q0 -21 -28 -21h-361q-27 0 -27 21q0 20 27 20h160v335h-118q-27 0 -27 21q0 20 27 20h159zM318 624v-104h-59v104h59z"/>
<glyph glyph-name="j" unicode="j"
d="M417 376h-241q-27 0 -27 21q0 20 27 20h282v-445q0 -70 -43.5 -114t-111.5 -44h-129q-27 0 -27 21q0 20 27 20h128q50 0 82.5 33t32.5 84v404zM414 624v-104h-59v104h59z"/>
<glyph glyph-name="k" unicode="k"
d="M185 180v-180h-95q-27 0 -27 21q0 20 27 20h54v522h-54q-27 0 -27 21q0 20 27 20h95v-375l173 147h-22q-27 0 -27 21q0 20 27 20h130q27 0 27 -20q0 -21 -27 -21h-47l-157 -131l207 -204h45q27 0 27 -20q0 -21 -27 -21h-131q-27 0 -27 21q0 20 27 20h28l-180 178z"/>
<glyph glyph-name="l" unicode="l"
d="M320 604v-563h160q28 0 28 -20q0 -21 -28 -21h-361q-27 0 -27 21q0 20 27 20h160v522h-117q-27 0 -27 21q0 20 27 20h158z"/>
<glyph glyph-name="m" unicode="m"
d="M112 417v-52q25 36 49 51t56 15q64 0 97 -71q55 71 113 71q43 0 74 -32t31 -76v-282h34q27 0 27 -20q0 -21 -27 -21h-74v319q0 28 -20 49.5t-46 21.5q-50 0 -104 -82v-267h34q27 0 27 -20q0 -21 -27 -21h-74v316q0 29 -19.5 51.5t-45.5 22.5q-50 0 -105 -82v-267h34
q27 0 27 -20q0 -21 -27 -21h-109q-26 0 -26 21q0 20 27 20h34v335h-34q-27 0 -27 21q0 20 27 20h74z"/>
<glyph glyph-name="n" unicode="n"
d="M319 390q-18 0 -34.5 -3.5t-31 -11t-24.5 -14t-21.5 -18.5t-16 -17.5t-14 -18t-10.5 -13.5v-253h45q27 0 27 -20q0 -21 -27 -21h-131q-28 0 -28 21q0 20 28 20h45v335h-34q-27 0 -27 21q0 20 27 20h75v-69q43 48 76 65.5t80 17.5q67 0 112 -39t45 -97v-254h34
q27 0 27 -20q0 -21 -27 -21h-109q-27 0 -27 21q0 20 27 20h34v247q0 43 -32.5 72.5t-87.5 29.5z"/>
<glyph glyph-name="o" unicode="o"
d="M300 431q96 0 162 -65.5t66 -160.5q0 -92 -67 -156.5t-161 -64.5q-95 0 -161.5 65t-66.5 159q0 92 66.5 157.5t161.5 65.5zM300 390q-78 0 -132.5 -53t-54.5 -129t54.5 -129.5t132.5 -53.5q77 0 132 53t55 127q0 79 -54 132t-133 53z"/>
<glyph glyph-name="p" unicode="p"
d="M144 417v-82q40 52 81 74t98 22q93 0 155.5 -60.5t62.5 -149.5t-63 -150t-155 -61q-112 0 -179 96v-251h98q27 0 27 -20q0 -21 -27 -21h-193q-27 0 -27 21q0 20 27 20h54v521h-54q-27 0 -27 21q0 20 27 20h95zM322 390q-74 0 -126 -49.5t-52 -119.5q0 -71 52 -120.5
t126 -49.5t126 49t52 119q0 73 -51 122t-127 49z"/>
<glyph glyph-name="q" unicode="q"
d="M461 335v82h95q27 0 27 -20q0 -21 -27 -21h-54v-521h54q27 0 27 -20q0 -21 -27 -21h-193q-27 0 -27 21q0 20 27 20h98v251q-67 -96 -180 -96q-92 0 -155 61t-63 150t63 149.5t156 60.5q111 0 179 -96zM282 390q-75 0 -126.5 -49t-51.5 -120t51.5 -120.5t126.5 -49.5
q74 0 126.5 49t52.5 119q0 73 -51.5 122t-127.5 49z"/>
<glyph glyph-name="r" unicode="r"
d="M520 344q-5 0 -31 21t-51 21q-34 0 -70.5 -24t-119.5 -100v-221h179q27 0 27 -21q0 -20 -27 -20h-316q-27 0 -27 21q0 20 27 20h96v335h-75q-27 0 -27 21q0 20 27 20h116v-102q69 63 112 87.5t81 24.5q40 0 70 -24t30 -38q0 -9 -6 -15t-15 -6z"/>
<glyph glyph-name="s" unicode="s"
d="M452 117q0 37 -34 57t-82 27t-96 15t-82 33t-34 69q0 49 48.5 81t122.5 32q85 0 137 -46v4q0 28 21 28q20 0 20 -28v-69q0 -27 -20 -27q-18 0 -21 23q-4 34 -40 54t-93 20q-56 0 -93 -21.5t-37 -53.5q0 -30 34 -46t82 -22.5t96 -16t82 -38t34 -76.5q0 -57 -55.5 -94.5
t-140.5 -37.5q-96 0 -157 54v-11q0 -27 -20 -27q-21 0 -21 27v83q0 27 21 27q20 0 20 -22v-7q0 -34 45 -58.5t109 -24.5q66 0 110 26t44 66z"/>
<glyph glyph-name="t" unicode="t"
d="M186 417h220q27 0 27 -20q0 -21 -27 -21h-220v-267q0 -38 30 -61t82 -23q42 0 89.5 11.5t76.5 28.5q10 6 16 6q7 0 13 -6t6 -14q0 -22 -67.5 -44.5t-131.5 -22.5q-71 0 -113 33.5t-42 89.5v269h-74q-28 0 -28 21q0 20 28 20h74v119q0 27 21 27q20 0 20 -27v-119z"/>
<glyph glyph-name="u" unicode="u"
d="M439 0v66q-84 -82 -182 -82q-60 0 -96.5 36t-36.5 95v261h-54q-27 0 -27 21q0 20 27 20h95v-302q0 -38 26.5 -64t64.5 -26q100 0 183 90v261h-74q-28 0 -28 21q0 20 28 20h115v-376h34q27 0 27 -20q0 -21 -27 -21h-75z"/>
<glyph glyph-name="v" unicode="v"
d="M336 0h-70l-168 376h-41q-27 0 -27 21q0 20 27 20h151q28 0 28 -20q0 -21 -28 -21h-65l150 -335h19l147 335h-68q-27 0 -27 21q0 20 27 20h152q27 0 27 -20q0 -21 -27 -21h-41z"/>
<glyph glyph-name="w" unicode="w"
d="M441 0h-50l-91 259l-89 -259h-51l-84 376h-19q-27 0 -27 21q0 20 27 20h111q27 0 27 -20q0 -21 -27 -21h-53l72 -320l86 255h51l89 -255l69 320h-50q-27 0 -27 21q0 20 27 20h111q27 0 27 -20q0 -21 -27 -21h-19z"/>
<glyph glyph-name="x" unicode="x"
d="M329 219l184 -178h9q27 0 27 -20q0 -21 -27 -21h-132q-27 0 -27 21q0 20 27 20h65l-155 149l-157 -149h68q27 0 27 -20q0 -21 -27 -21h-133q-27 0 -27 21q0 20 27 20h9l184 178l-163 157h-7q-27 0 -27 21q0 20 27 20h111q27 0 27 -20q0 -21 -27 -21h-46l134 -130l137 130
h-48q-27 0 -27 21q0 20 27 20h110q27 0 27 -20q0 -21 -27 -21h-7z"/>
<glyph glyph-name="y" unicode="y"
d="M282 0l-188 376h-16q-27 0 -27 21q0 20 27 20h115q27 0 27 -20q0 -21 -27 -21h-52l164 -331l161 331h-54q-27 0 -27 21q0 20 27 20h110q27 0 27 -20q0 -21 -27 -21h-14l-256 -521h65q27 0 27 -20q0 -21 -27 -21h-233q-27 0 -27 21q0 20 27 20h127z"/>
<glyph glyph-name="z" unicode="z"
d="M474 417v-36l-303 -340h277v56q0 27 21 27q20 0 20 -27v-97h-374v36l301 340h-251v-55q0 -27 -20 -27q-21 0 -21 27v96h350z"/>
<glyph glyph-name="braceleft" unicode="{"
d="M410 -104q0 -20 -21 -20q-52 0 -80 29.5t-28 80.5v148q0 43 -33.5 65t-69.5 22q-21 0 -21 19q0 21 21 21q36 0 69.5 21.5t33.5 63.5v147q0 52 28 81.5t80 29.5q9 0 15 -5.5t6 -14.5q0 -8 -6.5 -14t-15.5 -6q-25 0 -45.5 -16.5t-20.5 -54.5v-147q0 -76 -78 -106
q78 -24 78 -106v-148q0 -37 20.5 -53.5t46.5 -16.5q9 0 15 -6t6 -14z"/>
<glyph glyph-name="bar" unicode="|"
d="M320 577v-674q0 -27 -20 -27t-20 27v674q0 27 20 27t20 -27z"/>
<glyph glyph-name="braceright" unicode="}"
d="M190 -104q0 8 6 14t15 6q26 0 46.5 16.5t20.5 53.5v148q0 82 78 106q-78 30 -78 106v147q0 38 -20.5 54.5t-45.5 16.5q-9 0 -15.5 6t-6.5 14q0 9 6 14.5t15 5.5q52 0 80 -29.5t28 -81.5v-147q0 -42 33.5 -63.5t69.5 -21.5q21 0 21 -21q0 -19 -21 -19q-36 0 -69.5 -22
t-33.5 -65v-148q0 -51 -28 -80.5t-80 -29.5q-21 0 -21 20z"/>
<glyph glyph-name="asciitilde" unicode="~"
d="M386 253q22 0 43 19.5t36 38.5t23 19t14 -5.5t6 -13.5q0 -19 -41 -59t-78 -40q-30 0 -63 24t-62.5 47.5t-51.5 23.5q-19 0 -35 -12t-25 -26.5t-20 -26.5t-21 -12q-7 0 -13 6t-6 13q0 17 38 58t82 41q30 0 64.5 -24t64 -47.5t45.5 -23.5z"/>
<glyph glyph-name="nonbreakingspace" unicode="&#xa0;"
/>
<glyph glyph-name="exclamdown" unicode="&#xa1;"
d="M262 -110l18 326q1 28 21 28q19 0 20 -28l18 -326q1 -7 1 -16q0 -17 -11.5 -28t-28.5 -11q-16 0 -27.5 11.5t-11.5 27.5l1 6v10zM307 368h-13q-23 0 -38.5 14.5t-15.5 35.5t15.5 35.5t38.5 14.5h13q22 0 37.5 -14.5t15.5 -35.5t-15.5 -35.5t-37.5 -14.5z"/>
<glyph glyph-name="cent" unicode="&#xa2;"
d="M113 311q0 19 6 41t21.5 52.5t51.5 55.5t88 36v107q0 27 21 27q20 0 20 -27v-106q63 0 104 -36q5 17 20 17q21 0 21 -25v-61q0 -28 -20 -28q-17 0 -20 23q-5 32 -36 51.5t-79 19.5q-68 0 -112.5 -41.5t-44.5 -105.5q0 -63 43.5 -104.5t109.5 -41.5q44 0 73.5 12.5t45 25
t24.5 12.5q19 0 19 -20t-49 -43t-99 -25v-113q0 -27 -20 -27q-21 0 -21 27v113q-73 8 -120 60.5t-47 123.5z"/>
<glyph glyph-name="sterling" unicode="&#xa3;"
d="M198 435q0 -25 27 -121h99q27 0 27 -21q0 -20 -27 -20h-90q5 -35 5 -57q0 -104 -57 -175h252q19 0 29.5 11.5t13 25.5t8 25.5t15.5 11.5q20 0 20 -22q0 -38 -24.5 -65.5t-59.5 -27.5h-323q-27 0 -27 21q0 19 22 19q35 2 62.5 56.5t27.5 123.5q0 27 -4 53h-104
q-27 0 -27 21q0 20 27 20h94q-27 85 -27 121q0 59 41.5 101t99.5 42q51 0 90 -32t39 -51q0 -7 -6.5 -13t-13.5 -6t-20 15t-35.5 30.5t-52.5 15.5q-41 0 -71 -30.5t-30 -71.5z"/>
<glyph glyph-name="currency" unicode="&#xa4;"
d="M199 162l-57 -56q-10 -11 -18 -11q-9 0 -15 6t-6 14q0 9 11 20l56 56q-35 47 -35 101q0 59 34 102l-55 56q-11 10 -11 19q0 8 6 14t14 6t19 -11l56 -55q44 35 102 35q57 0 102 -35l55 55q11 11 20 11q8 0 14 -6t6 -15q0 -8 -10 -18l-55 -56q34 -47 34 -101
q0 -58 -35 -102l55 -56q10 -10 10 -20q0 -8 -6 -14t-14 -6q-9 0 -19 11l-56 56q-47 -35 -101 -35q-55 0 -101 35zM300 417q-51 0 -87.5 -36.5t-36.5 -87.5q0 -52 36.5 -88.5t87.5 -36.5q50 0 87 36.5t37 86.5q0 53 -36 89.5t-88 36.5z"/>
<glyph glyph-name="yen" unicode="&#xa5;"
d="M321 238v-60h131q21 0 21 -18q0 -19 -21 -19h-131v-100h86q27 0 27 -20q0 -21 -27 -21h-213q-27 0 -27 21q0 20 27 20h86v100h-130q-22 0 -22 19q0 18 22 18h130v60h-130q-22 0 -22 19q0 18 22 18h116l-164 247h-24q-27 0 -27 21q0 20 27 20h110q27 0 27 -20
q0 -21 -27 -21h-39l151 -227l151 227h-39q-27 0 -27 21q0 20 27 20h110q27 0 27 -20q0 -21 -27 -21h-24l-164 -247h118q21 0 21 -18q0 -19 -21 -19h-131z"/>
<glyph glyph-name="brokenbar" unicode="&#xa6;"
d="M320 577v-267q0 -27 -20 -27t-20 27v267q0 27 20 27t20 -27zM320 170v-267q0 -27 -20 -27t-20 27v267q0 27 20 27t20 -27z"/>
<glyph glyph-name="section" unicode="&#xa7;"
d="M534 185q0 -72 -102 -84q13 -22 13 -49q0 -49 -38 -81.5t-95 -32.5h-207v111q0 24 21 24q20 0 20 -24v-71h165q40 0 66.5 21.5t26.5 53.5q0 26 -29.5 54t-101.5 68l-65 36q-142 80 -142 147q0 34 28 56.5t75 26.5q-13 22 -13 46q0 47 38 81.5t90 34.5h212v-109
q0 -24 -20 -24q-21 0 -21 24v69h-168q-37 0 -64 -22.5t-27 -53.5q0 -28 30 -54t119 -75l56 -31q70 -38 101.5 -71.5t31.5 -70.5zM400 139q4 2 29.5 5.5t43.5 12t18 27.5q0 22 -29 51t-78 55l-64 36q-84 46 -119 77q-6 -1 -18 -1q-32 0 -53 -13t-21 -32q0 -43 125 -114
l62 -35q80 -45 104 -69z"/>
<glyph glyph-name="dieresis" unicode="&#xa8;"
d="M190 611q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15zM411 611q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15z"/>
<glyph glyph-name="copyright" unicode="&#xa9;"
d="M425 185q0 -9 -14.5 -26t-44.5 -32.5t-64 -15.5q-59 0 -104 45t-45 104v45q0 60 41 102.5t98 42.5q43 0 89 -29q0 22 18 22t18 -20v-65q0 -20 -18 -20q-15 0 -18 19q-2 23 -28 40t-60 17q-43 0 -73.5 -32.5t-30.5 -79.5v-39q0 -47 33.5 -81.5t79.5 -34.5q25 0 44 8.5
t28 19t18 19t16 8.5t12 -5t5 -12zM300 578q122 0 209 -87t87 -209q0 -125 -87 -211t-213 -86q-120 0 -206.5 87.5t-86.5 209.5t87 209t210 87zM300 537q-106 0 -181 -74.5t-75 -180.5q0 -105 74.5 -180.5t177.5 -75.5q109 0 184 74.5t75 181.5q0 105 -75 180t-180 75z"/>
<glyph glyph-name="ordfeminine" unicode="&#xaa;"
d="M189 533q0 18 42.5 29.5t66.5 11.5q44 0 72 -23.5t28 -60.5v-171h26q23 0 23 -19q0 -18 -23 -18h-63v26q-54 -29 -107 -29q-44 0 -71.5 22t-27.5 57q0 43 37.5 70t95.5 27q28 0 73 -9v44q0 21 -18 34t-47 13q-21 0 -54.5 -11.5t-35.5 -11.5q-7 0 -12 5.5t-5 13.5z
M361 350v59q-29 11 -71 11q-43 0 -70.5 -18t-27.5 -46q0 -18 17 -29t45 -11q55 0 107 34z"/>
<glyph glyph-name="guillemotleft" unicode="&#xab;"
d="M63 209l208 197q11 11 21 11q8 0 14 -6t6 -14t-11 -20l-143 -168l143 -169q11 -12 11 -20t-6 -14t-14 -6t-21 11zM292 209l208 197q11 11 21 11q8 0 14 -6t6 -14t-11 -20l-143 -168l143 -169q11 -12 11 -20t-6 -14t-14 -6t-21 11z"/>
<glyph glyph-name="logicalnot" unicode="&#xac;"
d="M487 397h-388q-27 0 -27 21q0 20 27 20h429v-243q0 -27 -21 -27q-20 0 -20 27v202z"/>
<glyph glyph-name="softhyphen" unicode="&#xad;"
d="M501 258h-402q-27 0 -27 21q0 20 27 20h402q27 0 27 -20q0 -21 -27 -21z"/>
<glyph glyph-name="registered" unicode="&#xae;"
d="M233 258v-102h36q20 0 20 -18t-20 -18h-96q-21 0 -21 18t21 18h24v251h-24q-21 0 -21 18t21 18h138q46 0 75.5 -26t29.5 -67q0 -28 -19 -50t-51 -31q25 -19 39.5 -39t45.5 -74h15q20 0 20 -18t-20 -18h-39q-57 119 -111 138h-63zM233 294h56q42 0 66.5 15.5t24.5 42.5
q0 24 -19.5 39.5t-50.5 15.5h-77v-113zM300 578q122 0 209 -87t87 -209q0 -125 -87 -211t-213 -86q-120 0 -206.5 87.5t-86.5 209.5t87 209t210 87zM300 537q-106 0 -181 -74.5t-75 -180.5q0 -105 74.5 -180.5t177.5 -75.5q109 0 184 74.5t75 181.5q0 105 -75 180t-180 75z
"/>
<glyph glyph-name="macron" unicode="&#xaf;"
d="M418 536h-236q-27 0 -27 20t27 20h236q27 0 27 -20t-27 -20z"/>
<glyph glyph-name="degree" unicode="&#xb0;"
d="M300 636q60 0 102.5 -42.5t42.5 -102.5t-42.5 -102.5t-102.5 -42.5q-61 0 -103 42.5t-42 104.5q0 58 43 100.5t102 42.5zM300 595q-43 0 -73.5 -30.5t-30.5 -72.5q0 -44 30.5 -74.5t73.5 -30.5t73.5 30.5t30.5 73.5t-30.5 73.5t-73.5 30.5z"/>
<glyph glyph-name="plusminus" unicode="&#xb1;"
d="M320 301v-162q0 -27 -20 -27q-21 0 -21 27v162h-180q-27 0 -27 20t27 20h180v161q0 27 21 27q20 0 20 -27v-161h180q28 0 28 -20t-28 -20h-180zM99 40h402q27 0 27 -20t-27 -20h-402q-27 0 -27 20t27 20z"/>
<glyph glyph-name="twosuperior" unicode="&#xb2;"
d="M186 525q0 26 32 56.5t77 30.5q43 0 75.5 -30t32.5 -71q0 -32 -29.5 -66.5t-154.5 -149.5h150v3q0 20 18 20t18 -20v-39h-230v43l132 123q38 38 49 54t11 32q0 26 -21.5 45.5t-50.5 19.5q-26 0 -46.5 -14.5t-27.5 -37.5q-4 -16 -18 -16q-7 0 -12 5t-5 12z"/>
<glyph glyph-name="threesuperior" unicode="&#xb3;"
d="M300 576q-21 0 -37 -6t-22.5 -12.5t-13 -12.5t-12.5 -6q-7 0 -12.5 5t-5.5 12q0 19 34.5 37.5t68.5 18.5q44 0 74 -26.5t30 -65.5q0 -43 -46 -74q58 -36 58 -88q0 -44 -35.5 -75.5t-85.5 -31.5q-37 0 -75.5 18t-38.5 35q0 7 5 12t12 5q3 0 32 -17t65 -17q34 0 59.5 21
t25.5 50q0 30 -26 51t-66 22q-21 2 -21 18q0 11 8 14.5t29 3.5q28 0 46 15t18 38q0 24 -19.5 40t-48.5 16z"/>
<glyph glyph-name="acute" unicode="&#xb4;"
d="M433 599l-114 -100q-12 -9 -19 -9q-8 0 -14 6t-6 15q0 8 12 19l114 100q12 9 19 9q8 0 14 -6t6 -15q0 -8 -12 -19z"/>
<glyph glyph-name="micro" unicode="&#xb5;"
d="M165 16v-189q0 -27 -21 -27q-20 0 -20 27v550h-54q-27 0 -27 20t27 20h95v-302q0 -41 25.5 -66t65.5 -25q101 0 183 91v262h-74q-28 0 -28 20t28 20h115v-377h34q27 0 27 -20t-27 -20h-75v59q-80 -75 -181 -75q-57 0 -93 32z"/>
<glyph glyph-name="paragraph" unicode="&#xb6;"
d="M284 260q-92 6 -148.5 46t-56.5 100v47q0 67 60.5 109t158.5 42h200q25 0 25 -20q0 -21 -25 -21h-61v-584h62q26 0 26 -20q0 -21 -26 -21h-98q-25 0 -25 21q0 18 21 20v584h-72v-584q21 -2 21 -20q0 -21 -25 -21h-134q-26 0 -26 21q0 20 26 20h97v281zM284 307v251
q-58 -6 -96.5 -36.5t-38.5 -70.5v-37q0 -40 38.5 -70.5t96.5 -36.5z"/>
<glyph glyph-name="middot" unicode="&#xb7;"
d="M300 280q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15z"/>
<glyph glyph-name="cedilla" unicode="&#xb8;"
d="M230 -114q4 0 26 -10.5t40 -10.5q19 0 31.5 8t12.5 21q0 25 -46 25h-20v81h38v-47q65 -2 65 -61q0 -29 -22 -47t-57 -18q-26 0 -57 11.5t-31 28.5q0 8 6 13.5t14 5.5z"/>
<glyph glyph-name="onesuperior" unicode="&#xb9;"
d="M319 608v-317h71q20 0 20 -18t-20 -18h-178q-21 0 -21 18q0 19 31 19q39 0 61 -1v268l-65 -21q-8 -2 -9 -2q-6 0 -11.5 5.5t-5.5 12.5q0 11 16 18t111 36z"/>
<glyph glyph-name="ordmasculine" unicode="&#xba;"
d="M301 577q62 0 104.5 -43t42.5 -105q0 -59 -43.5 -102t-103.5 -43q-61 0 -104 43t-43 104q0 60 43 103t104 43zM301 540q-46 0 -78 -32t-32 -77q0 -46 32.5 -78t77.5 -32t77.5 32t32.5 76q0 47 -32 79t-78 32z"/>
<glyph glyph-name="guillemotright" unicode="&#xbb;"
d="M311 208l-207 -197q-11 -11 -21 -11q-8 0 -14 6t-6 14t11 20l142 168l-142 169q-11 12 -11 20t6 14t14 6t21 -11zM541 208l-207 -197q-11 -11 -21 -11q-8 0 -14 6t-6 14t11 20l142 168l-142 169q-11 12 -11 20t6 14t14 6t21 -11z"/>
<glyph glyph-name="onequarter" unicode="&#xbc;"
d="M144 612v-317h71q21 0 21 -18t-21 -18h-178q-21 0 -21 18q0 19 31 19q39 0 61 -1v268l-65 -21q-8 -2 -9 -2q-6 0 -11.5 5.5t-5.5 12.5q0 11 16 18t111 36zM492 477l-351 -414q-10 -12 -18 -12t-14 6t-6 14q0 7 9 17l350 414q11 12 18 12q8 0 14 -6t6 -14q0 -7 -8 -17z
M453 18q0 12 9 15t37 3h11v57h-149v34l128 218h57v-216h13q21 0 21 -18t-25 -18h-9v-57h13q21 0 21 -18t-21 -18h-85q-21 0 -21 18zM510 129v180h-2l-107 -180h109z"/>
<glyph glyph-name="onehalf" unicode="&#xbd;"
d="M485 477l-351 -414q-10 -12 -18 -12t-14 6t-6 14q0 7 9 17l350 414q11 12 18 12q8 0 14 -6t6 -14q0 -7 -8 -17zM151 612v-317h71q20 0 20 -18t-20 -18h-178q-21 0 -21 18q0 19 31 19q39 0 61 -1v268l-65 -21q-8 -2 -9 -2q-6 0 -11.5 5.5t-5.5 12.5q0 11 16 18t111 36z
M354 266q0 26 32 56.5t77 30.5q43 0 75.5 -30t32.5 -71q0 -32 -29.5 -66.5t-154.5 -149.5h150v3q0 20 18 20t18 -20v-39h-230v43l132 123q38 38 49 54t11 32q0 26 -21.5 45.5t-50.5 19.5q-26 0 -46.5 -14.5t-27.5 -37.5q-4 -16 -18 -16q-7 0 -12 5t-5 12z"/>
<glyph glyph-name="threequarters" unicode="&#xbe;"
d="M125 576q-21 0 -37 -6t-22.5 -12.5t-13 -12.5t-12.5 -6q-7 0 -12.5 5t-5.5 12q0 19 34.5 37.5t68.5 18.5q44 0 74 -26.5t30 -65.5q0 -43 -46 -74q58 -36 58 -88q0 -44 -35.5 -75.5t-85.5 -31.5q-37 0 -75.5 18t-38.5 35q0 7 5 12t12 5q3 0 32 -17t65 -17q34 0 59.5 21
t25.5 50q0 30 -26 51t-66 22q-21 2 -21 18q0 11 8 14.5t29 3.5q28 0 46 15t18 38q0 24 -19.5 40t-48.5 16zM491 477l-351 -414q-10 -12 -18 -12t-14 6t-6 14q0 7 9 17l350 414q11 12 18 12q8 0 14 -6t6 -14q0 -7 -8 -17zM453 18q0 12 9 15t37 3h11v57h-149v34l128 218h57
v-216h13q21 0 21 -18t-25 -18h-9v-57h13q21 0 21 -18t-21 -18h-85q-21 0 -21 18zM510 129v180h-2l-107 -180h109z"/>
<glyph glyph-name="questiondown" unicode="&#xbf;"
d="M466 -126q-99 -49 -175 -49q-80 0 -129 42.5t-49 111.5q0 56 35.5 94t129.5 82v41q0 27 20 27q21 0 21 -27v-68q-87 -36 -126 -71t-39 -78q0 -50 40 -81.5t102 -31.5q60 0 129 34v41q0 27 20 27q21 0 21 -27v-67zM313 317h-27q-25 0 -41 14t-16 36t16 36t41 14h27
q26 0 41.5 -14t15.5 -36t-15.5 -36t-41.5 -14z"/>
<glyph glyph-name="Agrave" unicode="&#xc0;"
d="M207 766l114 -100q12 -11 12 -19q0 -9 -6 -15t-15 -6q-7 0 -18 9l-114 100q-12 11 -12 20q0 8 6 14t14 6q7 0 19 -9zM428 188h-266l-54 -147h79q27 0 27 -20q0 -21 -27 -21h-151q-27 0 -27 21q0 20 27 20h31l179 481h-120q-27 0 -27 21q0 20 27 20h204l197 -522h37
q27 0 27 -20q0 -21 -27 -21h-156q-27 0 -27 21q0 20 27 20h76zM413 229l-112 293h-15l-108 -293h235z"/>
<glyph glyph-name="Aacute" unicode="&#xc1;"
d="M395 732l-114 -100q-12 -9 -19 -9q-8 0 -14 6t-6 15q0 8 12 19l114 100q12 9 19 9q8 0 14 -6t6 -15q0 -8 -12 -19zM428 188h-266l-54 -147h79q27 0 27 -20q0 -21 -27 -21h-151q-27 0 -27 21q0 20 27 20h31l179 481h-120q-27 0 -27 21q0 20 27 20h204l197 -522h37
q27 0 27 -20q0 -21 -27 -21h-156q-27 0 -27 21q0 20 27 20h76zM413 229l-112 293h-15l-108 -293h235z"/>
<glyph glyph-name="Acircumflex" unicode="&#xc2;"
d="M298 759l132 -108q13 -11 13 -21q0 -8 -6 -14t-14 -6q-7 0 -18 8l-107 88l-107 -88q-11 -8 -18 -8q-8 0 -14 6t-6 14t13 20zM428 188h-266l-54 -147h79q27 0 27 -20q0 -21 -27 -21h-151q-27 0 -27 21q0 20 27 20h31l179 481h-120q-27 0 -27 21q0 20 27 20h204l197 -522
h37q27 0 27 -20q0 -21 -27 -21h-156q-27 0 -27 21q0 20 27 20h76zM413 229l-112 293h-15l-108 -293h235z"/>
<glyph glyph-name="Atilde" unicode="&#xc3;"
d="M364 655q18 0 42 23t31 23q8 0 14 -6t6 -14q0 -15 -31.5 -41t-59.5 -26q-26 0 -69 24t-55 24q-25 0 -44 -22.5t-31 -22.5q-8 0 -14 5.5t-6 13.5q0 18 32.5 42.5t61.5 24.5q23 0 66.5 -24t56.5 -24zM428 188h-266l-54 -147h79q27 0 27 -20q0 -21 -27 -21h-151
q-27 0 -27 21q0 20 27 20h31l179 481h-120q-27 0 -27 21q0 20 27 20h204l197 -522h37q27 0 27 -20q0 -21 -27 -21h-156q-27 0 -27 21q0 20 27 20h76zM413 229l-112 293h-15l-108 -293h235z"/>
<glyph glyph-name="Adieresis" unicode="&#xc4;"
d="M187 709q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15zM408 709q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15zM428 188h-266l-54 -147h79q27 0 27 -20q0 -21 -27 -21h-151q-27 0 -27 21q0 20 27 20h31
l179 481h-120q-27 0 -27 21q0 20 27 20h204l197 -522h37q27 0 27 -20q0 -21 -27 -21h-156q-27 0 -27 21q0 20 27 20h76zM413 229l-112 293h-15l-108 -293h235z"/>
<glyph glyph-name="Aring" unicode="&#xc5;"
d="M296 794q39 0 66 -26.5t27 -65.5q0 -36 -27.5 -62.5t-65.5 -26.5t-65.5 27t-27.5 64t27.5 63.5t65.5 26.5zM296 761q-25 0 -42.5 -17t-17.5 -40q0 -24 17.5 -41t42.5 -17t42.5 17t17.5 40q0 24 -17.5 41t-42.5 17zM428 188h-266l-54 -147h79q27 0 27 -20q0 -21 -27 -21
h-151q-27 0 -27 21q0 20 27 20h31l179 481h-120q-27 0 -27 21q0 20 27 20h204l197 -522h37q27 0 27 -20q0 -21 -27 -21h-156q-27 0 -27 21q0 20 27 20h76zM413 229l-112 293h-15l-108 -293h235z"/>
<glyph glyph-name="AE" unicode="&#xc6;"
d="M297 189h-141l-41 -148h26q27 0 27 -20q0 -21 -27 -21h-104q-27 0 -27 21q0 20 27 20h37l133 481h-63q-27 0 -27 21q0 20 27 20h426v-152q0 -27 -21 -27q-20 0 -20 27v111h-191v-208h78v28q0 27 20 27t20 -27v-96q0 -27 -20 -27t-20 27v27h-78v-232h211v93q0 26 20 26
q21 0 21 -26v-134h-345q-27 0 -27 21q0 20 27 20h52v148zM297 230v292h-50l-80 -292h130z"/>
<glyph glyph-name="Ccedilla" unicode="&#xc7;"
d="M63 325q0 19 5.5 46.5t22.5 64.5t42 67t70 51.5t102 21.5q101 0 173 -69v29q0 27 21 27q20 0 20 -27v-112q0 -27 -21 -27q-18 0 -20 24q-3 46 -54.5 80t-118.5 34q-83 0 -142 -63.5t-59 -152.5v-71q0 -90 65 -156.5t153 -66.5q52 0 93 21t81 69q9 10 18 10q20 0 20 -19
q0 -16 -38 -51q-35 -32 -80 -51.5t-86 -19.5h-13v-31q65 -2 65 -61q0 -29 -22 -47t-57 -18q-26 0 -57 11.5t-31 28.5q0 8 6 13.5t14 5.5q4 0 26 -10.5t40 -10.5q19 0 31.5 8t12.5 21q0 25 -46 25h-20v69q-108 27 -162 102t-54 152v83z"/>
<glyph glyph-name="Egrave" unicode="&#xc8;"
d="M218 768l114 -100q12 -11 12 -19q0 -9 -6 -15t-15 -6q-7 0 -18 9l-114 100q-12 11 -12 20q0 8 6 14t14 6q7 0 19 -9zM165 272v-231h314v119q0 27 21 27q20 0 20 -27v-160h-450q-27 0 -27 21q0 20 27 20h54v481h-54q-27 0 -27 21q0 20 27 20h429v-139q0 -27 -21 -27
q-20 0 -20 27v98h-293v-209h145v45q0 27 21 27q20 0 20 -27v-131q0 -27 -20 -27q-21 0 -21 27v45h-145z"/>
<glyph glyph-name="Eacute" unicode="&#xc9;"
d="M377 735l-114 -100q-12 -9 -19 -9q-8 0 -14 6t-6 15q0 8 12 19l114 100q12 9 19 9q8 0 14 -6t6 -15q0 -8 -12 -19zM165 272v-231h314v119q0 27 21 27q20 0 20 -27v-160h-450q-27 0 -27 21q0 20 27 20h54v481h-54q-27 0 -27 21q0 20 27 20h429v-139q0 -27 -21 -27
q-20 0 -20 27v98h-293v-209h145v45q0 27 21 27q20 0 20 -27v-131q0 -27 -20 -27q-21 0 -21 27v45h-145z"/>
<glyph glyph-name="Ecircumflex" unicode="&#xca;"
d="M288 755l132 -108q13 -11 13 -21q0 -8 -6 -14t-14 -6q-7 0 -18 8l-107 88l-107 -88q-11 -8 -18 -8q-8 0 -14 6t-6 14t13 20zM165 272v-231h314v119q0 27 21 27q20 0 20 -27v-160h-450q-27 0 -27 21q0 20 27 20h54v481h-54q-27 0 -27 21q0 20 27 20h429v-139
q0 -27 -21 -27q-20 0 -20 27v98h-293v-209h145v45q0 27 21 27q20 0 20 -27v-131q0 -27 -20 -27q-21 0 -21 27v45h-145z"/>
<glyph glyph-name="Edieresis" unicode="&#xcb;"
d="M180 706q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15zM401 706q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15zM165 272v-231h314v119q0 27 21 27q20 0 20 -27v-160h-450q-27 0 -27 21q0 20 27 20h54
v481h-54q-27 0 -27 21q0 20 27 20h429v-139q0 -27 -21 -27q-20 0 -20 27v98h-293v-209h145v45q0 27 21 27q20 0 20 -27v-131q0 -27 -20 -27q-21 0 -21 27v45h-145z"/>
<glyph glyph-name="Igrave" unicode="&#xcc;"
d="M234 768l114 -100q12 -11 12 -19q0 -9 -6 -15t-15 -6q-7 0 -18 9l-114 100q-12 11 -12 20q0 8 6 14t14 6q7 0 19 -9zM320 522v-481h139q28 0 28 -20q0 -21 -28 -21h-319q-27 0 -27 21q0 20 27 20h139v481h-139q-27 0 -27 21q0 20 27 20h319q28 0 28 -20q0 -21 -28 -21
h-139z"/>
<glyph glyph-name="Iacute" unicode="&#xcd;"
d="M423 734l-114 -100q-12 -9 -19 -9q-8 0 -14 6t-6 15q0 8 12 19l114 100q12 9 19 9q8 0 14 -6t6 -15q0 -8 -12 -19zM320 522v-481h139q28 0 28 -20q0 -21 -28 -21h-319q-27 0 -27 21q0 20 27 20h139v481h-139q-27 0 -27 21q0 20 27 20h319q28 0 28 -20q0 -21 -28 -21h-139
z"/>
<glyph glyph-name="Icircumflex" unicode="&#xce;"
d="M300 760l132 -108q13 -11 13 -21q0 -8 -6 -14t-14 -6q-7 0 -18 8l-107 88l-107 -88q-11 -8 -18 -8q-8 0 -14 6t-6 14t13 20zM320 522v-481h139q28 0 28 -20q0 -21 -28 -21h-319q-27 0 -27 21q0 20 27 20h139v481h-139q-27 0 -27 21q0 20 27 20h319q28 0 28 -20
q0 -21 -28 -21h-139z"/>
<glyph glyph-name="Idieresis" unicode="&#xcf;"
d="M189 708q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15zM410 708q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15zM320 522v-481h139q28 0 28 -20q0 -21 -28 -21h-319q-27 0 -27 21q0 20 27 20h139v481
h-139q-27 0 -27 21q0 20 27 20h319q28 0 28 -20q0 -21 -28 -21h-139z"/>
<glyph glyph-name="Eth" unicode="&#xd0;"
d="M104 272h-77q-27 0 -27 21q0 20 27 20h77v209h-34q-27 0 -27 21q0 20 27 20h220q94 0 162 -74.5t68 -178.5v-56q0 -104 -68 -179t-162 -75h-220q-27 0 -27 21q0 20 27 20h34v231zM479 319q0 15 -5 37t-18.5 52t-34 54.5t-55.5 42t-78 17.5h-143v-209h162q27 0 27 -20
q0 -21 -27 -21h-162v-231h150q71 0 127.5 62.5t56.5 141.5v74z"/>
<glyph glyph-name="Ntilde" unicode="&#xd1;"
d="M354 657q18 0 42 23t31 23q8 0 14 -6t6 -14q0 -15 -31.5 -41t-59.5 -26q-26 0 -69 24t-55 24q-25 0 -44 -22.5t-31 -22.5q-8 0 -14 5.5t-6 13.5q0 18 32.5 42.5t61.5 24.5q23 0 66.5 -24t56.5 -24zM501 0h-52l-305 504v-463h75q27 0 27 -20q0 -21 -27 -21h-150
q-27 0 -27 21q0 20 27 20h34v481h-54q-27 0 -27 21q0 20 27 20h106l305 -504v463h-74q-28 0 -28 21q0 20 28 20h149q27 0 27 -20q0 -21 -27 -21h-34v-522z"/>
<glyph glyph-name="Ograve" unicode="&#xd2;"
d="M225 768l114 -100q12 -11 12 -19q0 -9 -6 -15t-15 -6q-7 0 -18 9l-114 100q-12 11 -12 20q0 8 6 14t14 6q7 0 19 -9zM300 576q106 0 177.5 -86.5t71.5 -213.5q0 -121 -73 -206.5t-176 -85.5q-104 0 -176.5 86t-72.5 210t72.5 210t176.5 86zM300 535q-86 0 -147 -75
t-61 -180t61 -180t147 -75q85 0 146.5 74.5t61.5 177.5q0 108 -60.5 183t-147.5 75z"/>
<glyph glyph-name="Oacute" unicode="&#xd3;"
d="M393 738l-114 -100q-12 -9 -19 -9q-8 0 -14 6t-6 15q0 8 12 19l114 100q12 9 19 9q8 0 14 -6t6 -15q0 -8 -12 -19zM300 576q106 0 177.5 -86.5t71.5 -213.5q0 -121 -73 -206.5t-176 -85.5q-104 0 -176.5 86t-72.5 210t72.5 210t176.5 86zM300 535q-86 0 -147 -75
t-61 -180t61 -180t147 -75q85 0 146.5 74.5t61.5 177.5q0 108 -60.5 183t-147.5 75z"/>
<glyph glyph-name="Ocircumflex" unicode="&#xd4;"
d="M300 759l132 -108q13 -11 13 -21q0 -8 -6 -14t-14 -6q-7 0 -18 8l-107 88l-107 -88q-11 -8 -18 -8q-8 0 -14 6t-6 14t13 20zM300 576q106 0 177.5 -86.5t71.5 -213.5q0 -121 -73 -206.5t-176 -85.5q-104 0 -176.5 86t-72.5 210t72.5 210t176.5 86zM300 535
q-86 0 -147 -75t-61 -180t61 -180t147 -75q85 0 146.5 74.5t61.5 177.5q0 108 -60.5 183t-147.5 75z"/>
<glyph glyph-name="Otilde" unicode="&#xd5;"
d="M362 655q18 0 42 23t31 23q8 0 14 -6t6 -14q0 -15 -31.5 -41t-59.5 -26q-26 0 -69 24t-55 24q-25 0 -44 -22.5t-31 -22.5q-8 0 -14 5.5t-6 13.5q0 18 32.5 42.5t61.5 24.5q23 0 66.5 -24t56.5 -24zM300 576q106 0 177.5 -86.5t71.5 -213.5q0 -121 -73 -206.5t-176 -85.5
q-104 0 -176.5 86t-72.5 210t72.5 210t176.5 86zM300 535q-86 0 -147 -75t-61 -180t61 -180t147 -75q85 0 146.5 74.5t61.5 177.5q0 108 -60.5 183t-147.5 75z"/>
<glyph glyph-name="Odieresis" unicode="&#xd6;"
d="M190 707q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15zM411 707q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15zM300 576q106 0 177.5 -86.5t71.5 -213.5q0 -121 -73 -206.5t-176 -85.5
q-104 0 -176.5 86t-72.5 210t72.5 210t176.5 86zM300 535q-86 0 -147 -75t-61 -180t61 -180t147 -75q85 0 146.5 74.5t61.5 177.5q0 108 -60.5 183t-147.5 75z"/>
<glyph glyph-name="multiply" unicode="&#xd7;"
d="M300 311l142 142q11 11 19 11t14.5 -6t6.5 -15q0 -8 -11 -19l-142 -142l142 -143q11 -10 11 -19q0 -8 -6 -14t-15 -6t-19 10l-142 143l-142 -143q-11 -10 -19 -10q-9 0 -15 6t-6 14q0 9 10 19l143 143l-143 142q-10 11 -10 19q0 9 6 15t15 6q7 0 18 -11z"/>
<glyph glyph-name="Oslash" unicode="&#xd8;"
d="M141 51l-65 -82q-9 -12 -18 -12q-8 0 -13 5.5t-5 12.5q0 6 8 16l69 87q-66 88 -66 202q0 124 72.5 210t176.5 86q86 0 158 -66l66 83q9 12 18 12q8 0 13 -5.5t5 -12.5q0 -5 -8 -16l-70 -88q67 -83 67 -203q0 -124 -72.5 -210t-176.5 -86q-90 0 -159 67zM144 112l288 364
q-59 59 -132 59q-86 0 -147 -75t-61 -180q0 -91 52 -168zM456 449l-289 -365q59 -59 133 -59q86 0 147 75t61 180q0 94 -52 169z"/>
<glyph glyph-name="Ugrave" unicode="&#xd9;"
d="M225 768l114 -100q12 -11 12 -19q0 -9 -6 -15t-15 -6q-7 0 -18 9l-114 100q-12 11 -12 20q0 8 6 14t14 6q7 0 19 -9zM499 522v-337q0 -85 -57.5 -143t-141.5 -58q-85 0 -142 57.5t-57 143.5v337h-34q-27 0 -27 21q0 20 27 20h149q27 0 27 -20q0 -21 -27 -21h-74v-337
q0 -68 45.5 -114t112.5 -46q66 0 112 46.5t46 113.5v337h-74q-27 0 -27 21q0 20 27 20h149q27 0 27 -20q0 -21 -27 -21h-34z"/>
<glyph glyph-name="Uacute" unicode="&#xda;"
d="M393 735l-114 -100q-12 -9 -19 -9q-8 0 -14 6t-6 15q0 8 12 19l114 100q12 9 19 9q8 0 14 -6t6 -15q0 -8 -12 -19zM499 522v-337q0 -85 -57.5 -143t-141.5 -58q-85 0 -142 57.5t-57 143.5v337h-34q-27 0 -27 21q0 20 27 20h149q27 0 27 -20q0 -21 -27 -21h-74v-337
q0 -68 45.5 -114t112.5 -46q66 0 112 46.5t46 113.5v337h-74q-27 0 -27 21q0 20 27 20h149q27 0 27 -20q0 -21 -27 -21h-34z"/>
<glyph glyph-name="Ucircumflex" unicode="&#xdb;"
d="M300 759l132 -108q13 -11 13 -21q0 -8 -6 -14t-14 -6q-7 0 -18 8l-107 88l-107 -88q-11 -8 -18 -8q-8 0 -14 6t-6 14t13 20zM499 522v-337q0 -85 -57.5 -143t-141.5 -58q-85 0 -142 57.5t-57 143.5v337h-34q-27 0 -27 21q0 20 27 20h149q27 0 27 -20q0 -21 -27 -21h-74
v-337q0 -68 45.5 -114t112.5 -46q66 0 112 46.5t46 113.5v337h-74q-27 0 -27 21q0 20 27 20h149q27 0 27 -20q0 -21 -27 -21h-34z"/>
<glyph glyph-name="Udieresis" unicode="&#xdc;"
d="M190 707q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15zM411 707q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15zM499 522v-337q0 -85 -57.5 -143t-141.5 -58q-85 0 -142 57.5t-57 143.5v337h-34
q-27 0 -27 21q0 20 27 20h149q27 0 27 -20q0 -21 -27 -21h-74v-337q0 -68 45.5 -114t112.5 -46q66 0 112 46.5t46 113.5v337h-74q-27 0 -27 21q0 20 27 20h149q27 0 27 -20q0 -21 -27 -21h-34z"/>
<glyph glyph-name="Yacute" unicode="&#xdd;"
d="M393 738l-114 -100q-12 -9 -19 -9q-8 0 -14 6t-6 15q0 8 12 19l114 100q12 9 19 9q8 0 14 -6t6 -15q0 -8 -12 -19zM322 254v-213h105q27 0 27 -20q0 -21 -27 -21h-251q-27 0 -27 21q0 20 27 20h105v213l-179 268h-24q-27 0 -27 21q0 20 27 20h111q27 0 27 -20
q0 -21 -27 -21h-38l152 -227l149 227h-40q-28 0 -28 21q0 20 28 20h110q27 0 27 -20q0 -21 -27 -21h-24z"/>
<glyph glyph-name="Thorn" unicode="&#xde;"
d="M165 111v-70h138q28 0 28 -20q0 -21 -28 -21h-233q-27 0 -27 21q0 20 27 20h54v481h-54q-27 0 -27 21q0 20 27 20h233q28 0 28 -20q0 -21 -28 -21h-138v-79h145q80 0 134.5 -47.5t54.5 -117.5t-59 -118.5t-144 -48.5h-131zM165 152h134q65 0 112 37t47 89
q0 51 -42.5 87.5t-101.5 36.5h-149v-250z"/>
<glyph glyph-name="germandbls" unicode="&#xdf;"
d="M456 162q0 48 -22 84t-54.5 54t-61 26t-49.5 8h-5q-27 0 -27 21q0 20 23 20h5q38 0 67 25.5t29 58.5q0 42 -33 73t-77 31q-42 0 -74 -28t-32 -65v-429h15q27 0 27 -20q0 -21 -27 -21h-110q-27 0 -27 21q0 20 27 20h54v429q0 53 44.5 93.5t102.5 40.5q64 0 107.5 -41.5
t43.5 -104.5q0 -60 -58 -99q153 -46 153 -198q0 -84 -36 -130.5t-101 -46.5q-51 0 -86 34t-35 83q0 23 20 23q11 0 18.5 -15.5t12.5 -34t23 -34t47 -15.5q46 0 71 35t25 102z"/>
<glyph glyph-name="agrave" unicode="&#xe0;"
d="M214 631l114 -100q12 -11 12 -19q0 -9 -6 -15t-15 -6q-7 0 -18 9l-114 100q-12 11 -12 20q0 8 6 14t14 6q7 0 19 -9zM125 378q0 22 68 37.5t106 15.5q70 0 115.5 -35t45.5 -88v-267h54q27 0 27 -20q0 -21 -27 -21h-95v67q-89 -83 -191 -83q-70 0 -113 35.5t-43 92.5
q0 65 58.5 105.5t152.5 40.5q59 0 136 -21v71q0 37 -34 59.5t-89 22.5q-46 0 -95 -16t-57 -16t-13.5 6t-5.5 14zM419 112v90q-60 15 -128 15q-79 0 -128.5 -29.5t-49.5 -76.5q0 -39 31 -62.5t83 -23.5q53 0 97.5 20t94.5 67z"/>
<glyph glyph-name="aacute" unicode="&#xe1;"
d="M382 600l-114 -100q-12 -9 -19 -9q-8 0 -14 6t-6 15q0 8 12 19l114 100q12 9 19 9q8 0 14 -6t6 -15q0 -8 -12 -19zM125 378q0 22 68 37.5t106 15.5q70 0 115.5 -35t45.5 -88v-267h54q27 0 27 -20q0 -21 -27 -21h-95v67q-89 -83 -191 -83q-70 0 -113 35.5t-43 92.5
q0 65 58.5 105.5t152.5 40.5q59 0 136 -21v71q0 37 -34 59.5t-89 22.5q-46 0 -95 -16t-57 -16t-13.5 6t-5.5 14zM419 112v90q-60 15 -128 15q-79 0 -128.5 -29.5t-49.5 -76.5q0 -39 31 -62.5t83 -23.5q53 0 97.5 20t94.5 67z"/>
<glyph glyph-name="acircumflex" unicode="&#xe2;"
d="M289 626l132 -108q13 -11 13 -21q0 -8 -6 -14t-14 -6q-7 0 -18 8l-107 88l-107 -88q-11 -8 -18 -8q-8 0 -14 6t-6 14t13 20zM125 378q0 22 68 37.5t106 15.5q70 0 115.5 -35t45.5 -88v-267h54q27 0 27 -20q0 -21 -27 -21h-95v67q-89 -83 -191 -83q-70 0 -113 35.5
t-43 92.5q0 65 58.5 105.5t152.5 40.5q59 0 136 -21v71q0 37 -34 59.5t-89 22.5q-46 0 -95 -16t-57 -16t-13.5 6t-5.5 14zM419 112v90q-60 15 -128 15q-79 0 -128.5 -29.5t-49.5 -76.5q0 -39 31 -62.5t83 -23.5q53 0 97.5 20t94.5 67z"/>
<glyph glyph-name="atilde" unicode="&#xe3;"
d="M361 532q18 0 42 23t31 23q8 0 14 -6t6 -14q0 -15 -31.5 -41t-59.5 -26q-26 0 -69 24t-55 24q-25 0 -44 -22.5t-31 -22.5q-8 0 -14 5.5t-6 13.5q0 18 32.5 42.5t61.5 24.5q23 0 66.5 -24t56.5 -24zM125 378q0 22 68 37.5t106 15.5q70 0 115.5 -35t45.5 -88v-267h54
q27 0 27 -20q0 -21 -27 -21h-95v67q-89 -83 -191 -83q-70 0 -113 35.5t-43 92.5q0 65 58.5 105.5t152.5 40.5q59 0 136 -21v71q0 37 -34 59.5t-89 22.5q-46 0 -95 -16t-57 -16t-13.5 6t-5.5 14zM419 112v90q-60 15 -128 15q-79 0 -128.5 -29.5t-49.5 -76.5q0 -39 31 -62.5
t83 -23.5q53 0 97.5 20t94.5 67z"/>
<glyph glyph-name="adieresis" unicode="&#xe4;"
d="M178 591q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15zM399 591q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15zM125 378q0 22 68 37.5t106 15.5q70 0 115.5 -35t45.5 -88v-267h54q27 0 27 -20
q0 -21 -27 -21h-95v67q-89 -83 -191 -83q-70 0 -113 35.5t-43 92.5q0 65 58.5 105.5t152.5 40.5q59 0 136 -21v71q0 37 -34 59.5t-89 22.5q-46 0 -95 -16t-57 -16t-13.5 6t-5.5 14zM419 112v90q-60 15 -128 15q-79 0 -128.5 -29.5t-49.5 -76.5q0 -39 31 -62.5t83 -23.5
q53 0 97.5 20t94.5 67z"/>
<glyph glyph-name="aring" unicode="&#xe5;"
d="M299 672q39 0 66 -26.5t27 -65.5q0 -36 -27.5 -62.5t-65.5 -26.5t-65.5 27t-27.5 64t27.5 63.5t65.5 26.5zM299 639q-25 0 -42.5 -17t-17.5 -40q0 -24 17.5 -41t42.5 -17t42.5 17t17.5 40q0 24 -17.5 41t-42.5 17zM125 378q0 22 68 37.5t106 15.5q70 0 115.5 -35
t45.5 -88v-267h54q27 0 27 -20q0 -21 -27 -21h-95v67q-89 -83 -191 -83q-70 0 -113 35.5t-43 92.5q0 65 58.5 105.5t152.5 40.5q59 0 136 -21v71q0 37 -34 59.5t-89 22.5q-46 0 -95 -16t-57 -16t-13.5 6t-5.5 14zM419 112v90q-60 15 -128 15q-79 0 -128.5 -29.5t-49.5 -76.5
q0 -39 31 -62.5t83 -23.5q53 0 97.5 20t94.5 67z"/>
<glyph glyph-name="ae" unicode="&#xe6;"
d="M71 378q0 21 49 37t79 16q37 0 68 -23t45 -61q18 39 50 61.5t68 22.5q73 0 109 -62.5t38 -169.5h-258q5 -76 41 -125t86 -49q25 0 48 15.5t39.5 31t24.5 15.5t14 -6t6 -14q0 -17 -46.5 -50t-87.5 -33q-74 0 -124 77v-34q0 -27 -20 -27t-20 27v25q-56 -68 -120 -68
t-106 42t-42 106q0 54 53.5 89.5t129.5 35.5q41 0 85 -12v60q0 35 -25.5 60t-62.5 25q-36 0 -67 -16t-35 -16q-7 0 -13 6.5t-6 13.5zM280 110v93q-44 13 -79 13q-32 0 -69 -12t-54 -28q-21 -21 -21 -55q0 -42 29.5 -69t75.5 -27q51 0 118 85zM535 240q-4 29 -8 48.5t-15 46
t-32 41t-51 14.5q-89 0 -109 -150h215z"/>
<glyph glyph-name="ccedilla" unicode="&#xe7;"
d="M535 88q0 -7 -8 -18t-26.5 -25t-43 -27t-62 -22.5t-80.5 -11.5v-31q65 -2 65 -61q0 -29 -22 -47t-57 -18q-26 0 -57 12t-31 27q0 8 6 14t14 6q4 0 26 -10.5t40 -10.5q19 0 31.5 8t12.5 21q0 25 -45 25h-21v67q-50 7 -87.5 28t-57 45.5t-31 55.5t-14.5 51t-3 38
q0 99 65 163t165 64q93 0 156 -55v13q0 28 21 28q20 0 20 -28v-91q0 -27 -20 -27t-21 24q-4 40 -50 67.5t-109 27.5q-83 0 -134.5 -51.5t-51.5 -133.5q0 -79 52 -129.5t134 -50.5q109 0 187 72q10 10 18 10t13.5 -5.5t5.5 -13.5z"/>
<glyph glyph-name="egrave" unicode="&#xe8;"
d="M226 631l114 -100q12 -11 12 -19q0 -9 -6 -15t-15 -6q-7 0 -18 9l-114 100q-12 11 -12 20q0 8 6 14t14 6q7 0 19 -9zM520 199h-416q11 -79 66 -126.5t136 -47.5q47 0 96 15t79 39q10 7 16 7q8 0 13.5 -6t5.5 -14q0 -26 -71.5 -54t-139.5 -28q-101 0 -171.5 68t-70.5 165
q0 91 66 152.5t162 61.5q101 0 165 -63.5t64 -168.5zM104 240h374q-12 68 -63 109t-124 41t-123 -40.5t-64 -109.5z"/>
<glyph glyph-name="eacute" unicode="&#xe9;"
d="M394 600l-114 -100q-12 -9 -19 -9q-8 0 -14 6t-6 15q0 8 12 19l114 100q12 9 19 9q8 0 14 -6t6 -15q0 -8 -12 -19zM520 199h-416q11 -79 66 -126.5t136 -47.5q47 0 96 15t79 39q10 7 16 7q8 0 13.5 -6t5.5 -14q0 -26 -71.5 -54t-139.5 -28q-101 0 -171.5 68t-70.5 165
q0 91 66 152.5t162 61.5q101 0 165 -63.5t64 -168.5zM104 240h374q-12 68 -63 109t-124 41t-123 -40.5t-64 -109.5z"/>
<glyph glyph-name="ecircumflex" unicode="&#xea;"
d="M303 624l132 -108q13 -11 13 -21q0 -8 -6 -14t-14 -6q-7 0 -18 8l-107 88l-107 -88q-11 -8 -18 -8q-8 0 -14 6t-6 14t13 20zM520 199h-416q11 -79 66 -126.5t136 -47.5q47 0 96 15t79 39q10 7 16 7q8 0 13.5 -6t5.5 -14q0 -26 -71.5 -54t-139.5 -28q-101 0 -171.5 68
t-70.5 165q0 91 66 152.5t162 61.5q101 0 165 -63.5t64 -168.5zM104 240h374q-12 68 -63 109t-124 41t-123 -40.5t-64 -109.5z"/>
<glyph glyph-name="edieresis" unicode="&#xeb;"
d="M194 591q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15zM415 591q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15zM520 199h-416q11 -79 66 -126.5t136 -47.5q47 0 96 15t79 39q10 7 16 7q8 0 13.5 -6
t5.5 -14q0 -26 -71.5 -54t-139.5 -28q-101 0 -171.5 68t-70.5 165q0 91 66 152.5t162 61.5q101 0 165 -63.5t64 -168.5zM104 240h374q-12 68 -63 109t-124 41t-123 -40.5t-64 -109.5z"/>
<glyph glyph-name="igrave" unicode="&#xec;"
d="M215 631l114 -100q12 -11 12 -19q0 -9 -6 -15t-15 -6q-7 0 -18 9l-114 100q-12 11 -12 20q0 8 6 14t14 6q7 0 19 -9zM320 417v-376h160q28 0 28 -20q0 -21 -28 -21h-361q-27 0 -27 21q0 20 27 20h160v335h-118q-27 0 -27 21q0 20 27 20h159z"/>
<glyph glyph-name="iacute" unicode="&#xed;"
d="M382 600l-114 -100q-12 -9 -19 -9q-8 0 -14 6t-6 15q0 8 12 19l114 100q12 9 19 9q8 0 14 -6t6 -15q0 -8 -12 -19zM320 417v-376h160q28 0 28 -20q0 -21 -28 -21h-361q-27 0 -27 21q0 20 27 20h160v335h-118q-27 0 -27 21q0 20 27 20h159z"/>
<glyph glyph-name="icircumflex" unicode="&#xee;"
d="M320 417v-376h160q28 0 28 -20q0 -21 -28 -21h-361q-27 0 -27 21q0 20 27 20h160v335h-118q-27 0 -27 21q0 20 27 20h159zM281 628l132 -108q13 -11 13 -21q0 -8 -6 -14t-14 -6q-7 0 -18 8l-107 88l-107 -88q-11 -8 -18 -8q-8 0 -14 6t-6 14t13 20z"/>
<glyph glyph-name="idieresis" unicode="&#xef;"
d="M320 417v-376h160q28 0 28 -20q0 -21 -28 -21h-361q-27 0 -27 21q0 20 27 20h160v335h-118q-27 0 -27 21q0 20 27 20h159zM176 591q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15zM397 591q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5
t-35 -14.5t-35 15t-15 35t15 35t35 15z"/>
<glyph glyph-name="eth" unicode="&#xf0;"
d="M277 549q-25 11 -48 18.5t-34.5 10t-20.5 5t-12 5.5t-3 10q0 10 6 16t15 6q47 0 148 -48l95 42l7 1q8 0 13.5 -6t5.5 -15q0 -13 -9 -17l-75 -34q75 -64 119 -151.5t44 -170.5q0 -108 -66 -173t-162 -65q-95 0 -161.5 65t-66.5 159q0 92 66 157.5t160 65.5q41 0 73 -12
t73 -41q-53 99 -121 148l-91 -42q-2 -1 -7 -1q-8 0 -14 7t-6 16q0 13 9 16zM301 390q-78 0 -132.5 -53t-54.5 -129t54.5 -129.5t132.5 -53.5q77 0 132 53t55 127q0 79 -54 132t-133 53z"/>
<glyph glyph-name="ntilde" unicode="&#xf1;"
d="M352 532q18 0 42 23t31 23q8 0 14 -6t6 -14q0 -15 -31.5 -41t-59.5 -26q-26 0 -69 24t-55 24q-25 0 -44 -22.5t-31 -22.5q-8 0 -14 5.5t-6 13.5q0 18 32.5 42.5t61.5 24.5q23 0 66.5 -24t56.5 -24zM319 390q-18 0 -34.5 -3.5t-31 -11t-24.5 -14t-21.5 -18.5t-16 -17.5
t-14 -18t-10.5 -13.5v-253h45q27 0 27 -20q0 -21 -27 -21h-131q-28 0 -28 21q0 20 28 20h45v335h-34q-27 0 -27 21q0 20 27 20h75v-69q43 48 76 65.5t80 17.5q67 0 112 -39t45 -97v-254h34q27 0 27 -20q0 -21 -27 -21h-109q-27 0 -27 21q0 20 27 20h34v247q0 43 -32.5 72.5
t-87.5 29.5z"/>
<glyph glyph-name="ograve" unicode="&#xf2;"
d="M228 631l114 -100q12 -11 12 -19q0 -9 -6 -15t-15 -6q-7 0 -18 9l-114 100q-12 11 -12 20q0 8 6 14t14 6q7 0 19 -9zM300 431q96 0 162 -65.5t66 -160.5q0 -92 -67 -156.5t-161 -64.5q-95 0 -161.5 65t-66.5 159q0 92 66.5 157.5t161.5 65.5zM300 390q-78 0 -132.5 -53
t-54.5 -129t54.5 -129.5t132.5 -53.5q77 0 132 53t55 127q0 79 -54 132t-133 53z"/>
<glyph glyph-name="oacute" unicode="&#xf3;"
d="M388 600l-114 -100q-12 -9 -19 -9q-8 0 -14 6t-6 15q0 8 12 19l114 100q12 9 19 9q8 0 14 -6t6 -15q0 -8 -12 -19zM300 431q96 0 162 -65.5t66 -160.5q0 -92 -67 -156.5t-161 -64.5q-95 0 -161.5 65t-66.5 159q0 92 66.5 157.5t161.5 65.5zM300 390q-78 0 -132.5 -53
t-54.5 -129t54.5 -129.5t132.5 -53.5q77 0 132 53t55 127q0 79 -54 132t-133 53z"/>
<glyph glyph-name="ocircumflex" unicode="&#xf4;"
d="M300 630l132 -108q13 -11 13 -21q0 -8 -6 -14t-14 -6q-7 0 -18 8l-107 88l-107 -88q-11 -8 -18 -8q-8 0 -14 6t-6 14t13 20zM300 431q96 0 162 -65.5t66 -160.5q0 -92 -67 -156.5t-161 -64.5q-95 0 -161.5 65t-66.5 159q0 92 66.5 157.5t161.5 65.5zM300 390
q-78 0 -132.5 -53t-54.5 -129t54.5 -129.5t132.5 -53.5q77 0 132 53t55 127q0 79 -54 132t-133 53z"/>
<glyph glyph-name="otilde" unicode="&#xf5;"
d="M362 532q18 0 42 23t31 23q8 0 14 -6t6 -14q0 -15 -31.5 -41t-59.5 -26q-26 0 -69 24t-55 24q-25 0 -44 -22.5t-31 -22.5q-8 0 -14 5.5t-6 13.5q0 18 32.5 42.5t61.5 24.5q23 0 66.5 -24t56.5 -24zM300 431q96 0 162 -65.5t66 -160.5q0 -92 -67 -156.5t-161 -64.5
q-95 0 -161.5 65t-66.5 159q0 92 66.5 157.5t161.5 65.5zM300 390q-78 0 -132.5 -53t-54.5 -129t54.5 -129.5t132.5 -53.5q77 0 132 53t55 127q0 79 -54 132t-133 53z"/>
<glyph glyph-name="odieresis" unicode="&#xf6;"
d="M190 591q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15zM411 591q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15zM300 431q96 0 162 -65.5t66 -160.5q0 -92 -67 -156.5t-161 -64.5q-95 0 -161.5 65
t-66.5 159q0 92 66.5 157.5t161.5 65.5zM300 390q-78 0 -132.5 -53t-54.5 -129t54.5 -129.5t132.5 -53.5q77 0 132 53t55 127q0 79 -54 132t-133 53z"/>
<glyph glyph-name="divide" unicode="&#xf7;"
d="M501 262h-402q-27 0 -27 20t27 20h402q27 0 27 -20t-27 -20zM300 124q21 0 35.5 -14.5t14.5 -35.5q0 -20 -15 -34.5t-35 -14.5t-34.5 14.5t-14.5 35.5q0 20 14.5 34.5t34.5 14.5zM300 540q21 0 35.5 -14.5t14.5 -35.5q0 -20 -15 -34.5t-35 -14.5t-34.5 14.5t-14.5 35.5
q0 20 14.5 34.5t34.5 14.5z"/>
<glyph glyph-name="oslash" unicode="&#xf8;"
d="M154 35l-66 -68q-10 -10 -17 -10t-12.5 5.5t-5.5 12.5t10 17l66 67q-57 66 -57 149q0 93 66.5 158t161.5 65q82 0 144 -49l64 66q11 10 18 10t12 -5.5t5 -12.5t-10 -17l-64 -65q59 -65 59 -151q0 -93 -66.5 -158t-161.5 -65q-80 0 -146 51zM441 329l-258 -264
q51 -40 117 -40q78 0 132.5 53t54.5 129q0 68 -46 122zM158 89l257 263q-50 38 -115 38q-78 0 -132.5 -53t-54.5 -129q0 -68 45 -119z"/>
<glyph glyph-name="ugrave" unicode="&#xf9;"
d="M220 631l114 -100q12 -11 12 -19q0 -9 -6 -15t-15 -6q-7 0 -18 9l-114 100q-12 11 -12 20q0 8 6 14t14 6q7 0 19 -9zM439 0v66q-84 -82 -182 -82q-60 0 -96.5 36t-36.5 95v261h-54q-27 0 -27 21q0 20 27 20h95v-302q0 -38 26.5 -64t64.5 -26q100 0 183 90v261h-74
q-28 0 -28 21q0 20 28 20h115v-376h34q27 0 27 -20q0 -21 -27 -21h-75z"/>
<glyph glyph-name="uacute" unicode="&#xfa;"
d="M373 600l-114 -100q-12 -9 -19 -9q-8 0 -14 6t-6 15q0 8 12 19l114 100q12 9 19 9q8 0 14 -6t6 -15q0 -8 -12 -19zM439 0v66q-84 -82 -182 -82q-60 0 -96.5 36t-36.5 95v261h-54q-27 0 -27 21q0 20 27 20h95v-302q0 -38 26.5 -64t64.5 -26q100 0 183 90v261h-74
q-28 0 -28 21q0 20 28 20h115v-376h34q27 0 27 -20q0 -21 -27 -21h-75z"/>
<glyph glyph-name="ucircumflex" unicode="&#xfb;"
d="M294 626l132 -108q13 -11 13 -21q0 -8 -6 -14t-14 -6q-7 0 -18 8l-107 88l-107 -88q-11 -8 -18 -8q-8 0 -14 6t-6 14t13 20zM439 0v66q-84 -82 -182 -82q-60 0 -96.5 36t-36.5 95v261h-54q-27 0 -27 21q0 20 27 20h95v-302q0 -38 26.5 -64t64.5 -26q100 0 183 90v261h-74
q-28 0 -28 21q0 20 28 20h115v-376h34q27 0 27 -20q0 -21 -27 -21h-75z"/>
<glyph glyph-name="udieresis" unicode="&#xfc;"
d="M182 591q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15zM403 591q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15zM439 0v66q-84 -82 -182 -82q-60 0 -96.5 36t-36.5 95v261h-54q-27 0 -27 21q0 20 27 20
h95v-302q0 -38 26.5 -64t64.5 -26q100 0 183 90v261h-74q-28 0 -28 21q0 20 28 20h115v-376h34q27 0 27 -20q0 -21 -27 -21h-75z"/>
<glyph glyph-name="yacute" unicode="&#xfd;"
d="M423 600l-114 -100q-12 -9 -19 -9q-8 0 -14 6t-6 15q0 8 12 19l114 100q12 9 19 9q8 0 14 -6t6 -15q0 -8 -12 -19zM282 0l-188 376h-16q-27 0 -27 21q0 20 27 20h115q27 0 27 -20q0 -21 -27 -21h-52l164 -331l161 331h-54q-27 0 -27 21q0 20 27 20h110q27 0 27 -20
q0 -21 -27 -21h-14l-256 -521h65q27 0 27 -20q0 -21 -27 -21h-233q-27 0 -27 21q0 20 27 20h127z"/>
<glyph glyph-name="thorn" unicode="&#xfe;"
d="M144 590v-255q40 52 81 74t98 22q93 0 155.5 -60.5t62.5 -149.5t-63 -150t-155 -61q-112 0 -179 96v-251h98q27 0 27 -20q0 -21 -27 -21h-193q-27 0 -27 21q0 20 27 20h54v694h-54q-27 0 -27 21q0 20 27 20h95zM322 390q-74 0 -126 -49.5t-52 -119.5q0 -71 52 -120.5
t126 -49.5t126 49t52 119q0 73 -51 122t-127 49z"/>
<glyph glyph-name="ydieresis" unicode="&#xff;"
d="M190 591q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15zM411 591q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15zM282 0l-188 376h-16q-27 0 -27 21q0 20 27 20h115q27 0 27 -20q0 -21 -27 -21h-52
l164 -331l161 331h-54q-27 0 -27 21q0 20 27 20h110q27 0 27 -20q0 -21 -27 -21h-14l-256 -521h65q27 0 27 -20q0 -21 -27 -21h-233q-27 0 -27 21q0 20 27 20h127z"/>
<glyph glyph-name="Iogonek" unicode="&#x12e;"
d="M429 -123q22 0 38 9.5t19 9.5q5 0 9.5 -4.5t4.5 -10.5q0 -13 -24.5 -24.5t-52.5 -11.5q-36 0 -56 16t-20 45q0 31 17.5 52.5t59.5 41.5h-284q-27 0 -27 21q0 20 27 20h139v481h-139q-27 0 -27 21q0 20 27 20h319q28 0 28 -20q0 -21 -28 -21h-139v-481h139q28 0 28 -20
q0 -9 -16 -20t-34.5 -21t-34.5 -28.5t-16 -40.5q0 -34 43 -34z"/>
<glyph glyph-name="obreve" unicode="&#x14f;"
d="M176 621q17 0 20 -22q2 -29 32 -48t72 -19t71.5 19t32.5 48q3 22 21 22q8 0 14 -6.5t6 -16.5q0 -45 -42 -76t-103 -31t-103 31t-42 76q0 23 21 23zM300 431q96 0 162 -65.5t66 -160.5q0 -92 -67 -156.5t-161 -64.5q-95 0 -161.5 65t-66.5 159q0 92 66.5 157.5t161.5 65.5
zM300 390q-78 0 -132.5 -53t-54.5 -129t54.5 -129.5t132.5 -53.5q77 0 132 53t55 127q0 79 -54 132t-133 53z"/>
<glyph glyph-name="Ohungarumlaut" unicode="&#x150;"
d="M345 705l-97 -94q-10 -9 -19 -9q-8 0 -14 6t-6 15q0 8 12 19l98 94q9 9 18 9q8 0 14 -6t6 -15q0 -7 -12 -19zM487 705l-98 -94q-11 -9 -19 -9t-14 6t-6 15t12 19l98 94q10 9 19 9q8 0 14 -6t6 -15q0 -8 -12 -19zM300 576q106 0 177.5 -86.5t71.5 -213.5
q0 -121 -73 -206.5t-176 -85.5q-104 0 -176.5 86t-72.5 210t72.5 210t176.5 86zM300 535q-86 0 -147 -75t-61 -180t61 -180t147 -75q85 0 146.5 74.5t61.5 177.5q0 108 -60.5 183t-147.5 75z"/>
<glyph glyph-name="OE" unicode="&#x152;"
d="M338 272v-231h211v92q0 27 20 27q21 0 21 -27v-133h-327q-114 0 -183.5 77.5t-69.5 204.5q0 125 68 203t177 78h315v-154q0 -27 -20 -27q-21 0 -21 27v113h-191v-209h78v27q0 27 21 27q20 0 20 -27v-96q0 -27 -20 -27q-21 0 -21 27v28h-78zM51 281q0 -41 10 -80
t32.5 -76.5t66.5 -60.5t105 -23h32v481h-32q-61 0 -105 -23t-66.5 -60.5t-32.5 -76.5t-10 -81z"/>
<glyph glyph-name="oe" unicode="&#x153;"
d="M299 317q45 114 132 114q24 0 47.5 -10.5t47 -35t37.5 -72.5t14 -114h-258q6 -74 42.5 -124t84.5 -50q25 0 48 15.5t39.5 31t24.5 15.5t14 -6.5t6 -13.5q0 -17 -46.5 -50t-87.5 -33q-45 0 -83.5 32t-61.5 87q-22 -57 -56.5 -88t-76.5 -31q-62 0 -108 68t-46 160
q0 89 45.5 154t107.5 65q41 0 77 -31t57 -83zM536 240q-4 29 -8.5 48t-15 46t-32 41.5t-51.5 14.5q-89 0 -109 -150h216zM166 390q-45 0 -79 -54.5t-34 -127.5t34 -128t80 -55q44 0 78.5 55t34.5 126q0 74 -34 129t-80 55z"/>
<glyph glyph-name="Scaron" unicode="&#x160;"
d="M308 600l-132 109q-13 9 -13 20q0 8 6 14t14 6q7 0 18 -8l107 -88l107 88q11 8 18 8t13.5 -6.5t6.5 -13.5q0 -10 -13 -21zM464 153q0 42 -26 68t-64.5 34.5t-84 19t-84 22t-64.5 43t-26 81.5q0 66 52.5 110.5t131.5 44.5q86 0 146 -59v19q0 27 21 27q20 0 20 -27v-103
q0 -27 -20 -27t-21 24q-3 45 -44 75t-99 30q-62 0 -102.5 -33t-40.5 -82q0 -39 26 -63t64.5 -32.5t84 -20t84 -23.5t64.5 -45.5t26 -84.5q0 -73 -58 -120t-148 -47q-106 0 -169 72v-29q0 -27 -21 -27q-20 0 -20 27v112q0 27 21 27q19 0 20 -24q2 -49 51 -83t117 -34
q70 0 116.5 36.5t46.5 91.5z"/>
<glyph glyph-name="scaron" unicode="&#x161;"
d="M306 490l-132 109q-13 9 -13 20q0 8 6 14t14 6q7 0 18 -8l107 -88l107 88q11 8 18 8t13.5 -6.5t6.5 -13.5q0 -10 -13 -21zM452 117q0 37 -34 57t-82 27t-96 15t-82 33t-34 69q0 49 48.5 81t122.5 32q85 0 137 -46v4q0 28 21 28q20 0 20 -28v-69q0 -27 -20 -27
q-18 0 -21 23q-4 34 -40 54t-93 20q-56 0 -93 -21.5t-37 -53.5q0 -30 34 -46t82 -22.5t96 -16t82 -38t34 -76.5q0 -57 -55.5 -94.5t-140.5 -37.5q-96 0 -157 54v-11q0 -27 -20 -27q-21 0 -21 27v83q0 27 21 27q20 0 20 -22v-7q0 -34 45 -58.5t109 -24.5q66 0 110 26t44 66z
"/>
<glyph glyph-name="Ydieresis" unicode="&#x178;"
d="M190 722q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15zM411 722q21 0 35.5 -15t14.5 -36q0 -20 -15 -34.5t-35 -14.5t-35 15t-15 35t15 35t35 15zM322 254v-213h105q27 0 27 -20q0 -21 -27 -21h-251q-27 0 -27 21q0 20 27 20h105v213
l-179 268h-24q-27 0 -27 21q0 20 27 20h111q27 0 27 -20q0 -21 -27 -21h-38l152 -227l149 227h-40q-28 0 -28 21q0 20 28 20h110q27 0 27 -20q0 -21 -27 -21h-24z"/>
<glyph glyph-name="zacute" unicode="&#x17a;"
d="M377 600l-114 -100q-12 -9 -19 -9q-8 0 -14 6t-6 15q0 8 12 19l114 100q12 9 19 9q8 0 14 -6t6 -15q0 -8 -12 -19zM474 417v-36l-303 -340h277v56q0 27 21 27q20 0 20 -27v-97h-374v36l301 340h-251v-55q0 -27 -20 -27q-21 0 -21 27v96h350z"/>
<glyph glyph-name="Zcaron" unicode="&#x17d;"
d="M296 605l-132 109q-13 9 -13 20q0 8 6 14t14 6q7 0 18 -8l107 -88l107 88q11 8 18 8t13.5 -6.5t6.5 -13.5q0 -10 -13 -21zM497 0h-394v59l328 460v3h-269v-118q0 -28 -20 -28q-21 0 -21 28v159h349v-58l-328 -460v-4h314v141q0 27 20 27q21 0 21 -27v-182z"/>
<glyph glyph-name="zcaron" unicode="&#x17e;"
d="M299 491l-132 109q-13 9 -13 20q0 8 6 14t14 6q7 0 18 -8l107 -88l107 88q11 8 18 8t13.5 -6.5t6.5 -13.5q0 -10 -13 -21zM474 417v-36l-303 -340h277v56q0 27 21 27q20 0 20 -27v-97h-374v36l301 340h-251v-55q0 -27 -20 -27q-21 0 -21 27v96h350z"/>
<glyph glyph-name="florin" unicode="&#x192;"
d="M499 566q-2 0 -22.5 5.5t-41.5 5.5q-51 0 -82.5 -32t-31.5 -83v-87h96q27 0 27 -20q0 -21 -27 -21h-96v-276q0 -63 -43 -107t-104 -44q-25 0 -56 8t-31 26q0 8 5.5 14t12.5 6q3 0 26 -6.5t44 -6.5q46 0 75.5 32.5t29.5 83.5v270h-96q-27 0 -27 21q0 20 27 20h96v87
q0 65 43.5 110.5t107.5 45.5q25 0 56 -7.5t31 -24.5q0 -8 -5.5 -14t-13.5 -6z"/>
<glyph glyph-name="uni0197" unicode="&#x197;"
d="M279 321v201h-139q-27 0 -27 21q0 20 27 20h319q28 0 28 -20q0 -21 -28 -21h-139v-201h100q27 0 27 -20t-27 -20h-100v-240h139q28 0 28 -20q0 -21 -28 -21h-319q-27 0 -27 21q0 20 27 20h139v240h-95q-27 0 -27 20t27 20h95z"/>
<glyph glyph-name="circumflex" unicode="&#x2c6;"
d="M300 639l132 -108q13 -11 13 -21q0 -8 -6 -14t-14 -6q-7 0 -18 8l-107 88l-107 -88q-11 -8 -18 -8q-8 0 -14 6t-6 14t13 20z"/>
<glyph glyph-name="tilde" unicode="&#x2dc;"
d="M362 557q18 0 42 23t31 23q8 0 14 -6t6 -14q0 -15 -31.5 -41t-59.5 -26q-26 0 -69 24t-55 24q-25 0 -44 -22.5t-31 -22.5q-8 0 -14 5.5t-6 13.5q0 18 32.5 42.5t61.5 24.5q23 0 66.5 -24t56.5 -24z"/>
<glyph glyph-name="uni02EB" unicode="&#x2eb;"
d="M130 209h341q27 0 27 -20t-27 -20h-341v-153q0 -26 -20 -26t-20 26v336q0 27 20 27t20 -27v-143z"/>
<glyph glyph-name="uni02EC" unicode="&#x2ec;"
d="M300 -200l-132 109q-13 9 -13 20q0 8 6 14t14 6q7 0 18 -8l107 -88l107 88q11 8 18 8t13.5 -6.5t6.5 -13.5q0 -10 -13 -21z"/>
<glyph glyph-name="uni02FD" unicode="&#x2fd;"
d="M438 -159v85q0 27 22 27q20 0 20 -27v-126h-360v126q0 27 20 27q9 0 15.5 -7.5t6.5 -19.5v-85h276z"/>
<glyph glyph-name="uni02FE" unicode="&#x2fe;"
d="M438 -159v-41h-318v126q0 27 20 27q9 0 15.5 -7.5t6.5 -19.5v-85h276z"/>
<glyph glyph-name="uni02FF" unicode="&#x2ff;"
d="M60 -110l166 84q8 5 12 5q7 0 13.5 -7t6.5 -15q0 -11 -13 -18l-66 -29h340q27 0 27 -20.5t-27 -20.5h-340l66 -29q13 -7 13 -18q0 -8 -6.5 -15t-13.5 -7q-4 0 -12 5z"/>
<glyph glyph-name="gravecomb" unicode="&#x300;" horiz-adv-x="0"
d="M-406 630l114 -100q12 -11 12 -19q0 -9 -6 -15t-15 -6q-7 0 -18 9l-114 100q-12 11 -12 20q0 8 6 14t14 6q7 0 19 -9z"/>
<glyph glyph-name="acutecomb" unicode="&#x301;" horiz-adv-x="0"
d="M-167 599l-114 -100q-12 -9 -19 -9q-8 0 -14 6t-6 15q0 8 12 19l114 100q12 9 19 9q8 0 14 -6t6 -15q0 -8 -12 -19z"/>
<glyph glyph-name="circumflexcmb" unicode="&#x302;" horiz-adv-x="0"
d="M-300 639l132 -108q13 -11 13 -21q0 -8 -6 -14t-14 -6q-7 0 -18 8l-107 88l-107 -88q-11 -8 -18 -8q-8 0 -14 6t-6 14t13 20z"/>
<glyph glyph-name="Emcyrillic" unicode="&#x41c;"
d="M326 169h-46l-159 353h-8v-481h74q28 0 28 -20q0 -21 -28 -21h-149q-27 0 -27 21q0 20 27 20h34v481h-25q-27 0 -27 21q0 20 27 20h99l157 -348l154 348h100q27 0 27 -20q0 -21 -27 -21h-25v-481h34q27 0 27 -20q0 -21 -27 -21h-149q-27 0 -27 21q0 20 27 20h74v481h-8z
"/>
<glyph glyph-name="gecyrillic" unicode="&#x433;"
d="M486 285q0 -27 -21 -27q-20 0 -20 27v91h-217v-335h45q27 0 27 -20q0 -21 -27 -21h-131q-28 0 -28 21q0 20 28 20h45v335h-34q-27 0 -27 21q0 20 27 20h333v-132z"/>
<glyph glyph-name="endash" unicode="&#x2013;"
d="M501 261h-402q-27 0 -27 21q0 20 27 20h402q27 0 27 -20q0 -21 -27 -21z"/>
<glyph glyph-name="emdash" unicode="&#x2014;"
d="M572 261h-544q-27 0 -27 21q0 20 27 20h544q27 0 27 -20q0 -21 -27 -21z"/>
<glyph glyph-name="quoteleft" unicode="&#x2018;"
d="M358 369h-118v79l97 160q13 22 31 22q5 0 9 -1q20 -5 20 -25q0 -4 -2 -10l-45 -146z"/>
<glyph glyph-name="quoteright" unicode="&#x2019;"
d="M241 604h118v-79l-97 -160q-13 -22 -31 -22q-5 0 -9 1q-20 5 -20 25q0 4 2 10l45 146z"/>
<glyph glyph-name="quotesinglbase" unicode="&#x201a;"
d="M175 111h118v-79l-97 -160q-13 -22 -31 -22q-5 0 -9 1q-20 5 -20 25q0 4 2 10l45 146z"/>
<glyph glyph-name="quotedblleft" unicode="&#x201c;"
d="M184 369h-118v79l97 160q13 22 31 22q5 0 9 -1q20 -5 20 -25q0 -4 -2 -10l-45 -146zM404 369h-118v79l97 160q13 22 31 22q5 0 9 -1q20 -5 20 -25q0 -4 -2 -10l-45 -146z"/>
<glyph glyph-name="quotedblright" unicode="&#x201d;"
d="M346 604h118v-79l-97 -160q-13 -22 -31 -22q-5 0 -9 1q-20 5 -20 25q0 4 2 10l45 146zM133 604h118v-79l-97 -160q-13 -22 -31 -22q-5 0 -9 1q-20 5 -20 25q0 4 2 10l45 146z"/>
<glyph glyph-name="quotedblbase" unicode="&#x201e;"
d="M126 128h118v-79l-97 -160q-13 -22 -31 -22q-5 0 -9 1q-20 5 -20 25q0 4 2 10l45 146zM349 128h118v-79l-97 -160q-13 -22 -31 -22q-5 0 -9 1q-20 5 -20 25q0 4 2 10l45 146z"/>
<glyph glyph-name="dagger" unicode="&#x2020;"
d="M124 396q0 17 20 21q19 0 66.5 0.5t68.5 0.5v159q0 27 21 27q20 0 20 -27v-160h128q28 0 28 -21q0 -20 -28 -20h-128v-412q0 -27 -20 -27q-21 0 -21 27v412h-128q-27 0 -27 20z"/>
<glyph glyph-name="daggerdbl" unicode="&#x2021;"
d="M124 396q0 17 20 21q19 0 66.5 0.5t68.5 0.5v159q0 27 21 27q20 0 20 -27v-160h128q28 0 28 -21q0 -20 -28 -20h-128v-211h128q28 0 28 -20q0 -21 -28 -21h-128v-159q0 -27 -20 -27q-21 0 -21 27v159h-128q-27 0 -27 21q0 20 27 20h128v211h-128q-27 0 -27 20z"/>
<glyph glyph-name="bullet" unicode="&#x2022;"
d="M302 337q39 0 67.5 -29t28.5 -69q0 -41 -29 -69.5t-69 -28.5q-41 0 -69.5 29t-28.5 70t29 69t71 28z"/>
<glyph glyph-name="ellipsis" unicode="&#x2026;"
d="M100 84q21 0 35.5 -14.5t14.5 -35.5q0 -20 -14.5 -34.5t-34.5 -14.5q-21 0 -35.5 14.5t-14.5 34.5q0 21 14.5 35.5t34.5 14.5zM299 84q21 0 35.5 -14.5t14.5 -35.5q0 -20 -15 -34.5t-35 -14.5t-34.5 14.5t-14.5 34.5t14.5 35t34.5 15zM500 84q20 0 34.5 -14.5t14.5 -35.5
q0 -20 -14.5 -34.5t-34.5 -14.5q-21 0 -35.5 14.5t-14.5 35.5q0 20 15 34.5t35 14.5z"/>
<glyph glyph-name="perthousand" unicode="&#x2030;"
d="M144 614q46 0 78 -33t32 -80q0 -45 -32.5 -77.5t-77.5 -32.5t-77.5 33t-32.5 79t32.5 78.5t77.5 32.5zM144 580q-31 0 -53.5 -22.5t-22.5 -54.5t22.5 -55t53.5 -23t53.5 22.5t22.5 54.5q0 33 -22 55.5t-54 22.5zM496 388l-374 -122q-8 -2 -11 -2q-5 0 -10 5t-5 11
q0 13 16 17l374 122q8 2 11 2q6 0 10.5 -5t4.5 -11q0 -13 -16 -17zM182 214q46 0 78 -33t32 -80q0 -45 -32.5 -77.5t-77.5 -32.5t-77.5 33t-32.5 79t32.5 78.5t77.5 32.5zM182 180q-31 0 -53.5 -22.5t-22.5 -54.5t22.5 -55t53.5 -23t53.5 22.5t22.5 54.5q0 33 -22 55.5
t-54 22.5zM454 214q46 0 78 -33t32 -80q0 -45 -32.5 -77.5t-77.5 -32.5t-77.5 33t-32.5 79t32.5 78.5t77.5 32.5zM454 180q-31 0 -53.5 -22.5t-22.5 -54.5t22.5 -55t53.5 -23t53.5 22.5t22.5 54.5q0 33 -22 55.5t-54 22.5z"/>
<glyph glyph-name="guilsinglleft" unicode="&#x2039;"
d="M63 209l208 197q11 11 21 11q8 0 14 -6t6 -14t-11 -20l-143 -168l143 -169q11 -12 11 -20t-6 -14t-14 -6t-21 11z"/>
<glyph glyph-name="guilsinglright" unicode="&#x203a;"
d="M541 208l-207 -197q-11 -11 -21 -11q-8 0 -14 6t-6 14t11 20l142 168l-142 169q-11 12 -11 20t6 14t14 6t21 -11z"/>
<glyph glyph-name="Euro" unicode="&#x20ac;"
d="M343 25q44 0 77.5 12.5t47.5 28t27 28t22 12.5q20 0 20 -19q0 -9 -13 -25t-36 -34t-62 -31t-83 -13q-95 0 -170.5 68.5t-86.5 164.5h-32q-22 0 -22 19q0 18 22 18h30v60h-30q-22 0 -22 19q0 18 22 18h32q11 97 79.5 161t160.5 64q52 0 99.5 -15.5t64.5 -42.5l5 21
q5 22 20 22q21 0 21 -21q0 -5 -1 -11l-23 -94q-6 -22 -21 -22q-8 0 -14.5 5t-6.5 16q0 3 1 9.5t1 11.5q0 36 -41.5 58t-104.5 22q-75 0 -130 -51t-69 -133h300q21 0 21 -18q0 -19 -21 -19h-302v-60h273q21 0 21 -18q0 -19 -21 -19h-271q17 -82 77.5 -137t138.5 -55z"/>
<glyph glyph-name="trademark" unicode="&#x2122;"
d="M141 530v-254h54q19 0 19 -16q0 -17 -19 -17h-140q-20 0 -20 17q0 16 20 16h53v254h-71v-73q0 -19 -16 -19q-17 0 -17 19v106h242v-106q0 -19 -17 -19q-7 0 -12 5.5t-5 13.5v73h-71zM598 260q0 -17 -20 -17h-85q-18 0 -18 17q0 16 19 16h37v244l-82 -184h-32l-83 184
v-244h37q19 0 19 -16q0 -17 -19 -17h-85q-18 0 -18 17q0 16 19 16h14v254h-10q-19 0 -19 17q0 16 19 16h59l83 -184l81 184h60q19 0 19 -16q0 -17 -19 -17h-10v-254h15q19 0 19 -16z"/>
</font>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,3 @@
export const environment = {
production: true
};

View File

@ -0,0 +1,8 @@
// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `.angular-cli.json`.
export const environment = {
production: false
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,12 +0,0 @@
<svg width="200.00000000000003" height="200.00000000000003" xmlns="http://www.w3.org/2000/svg">
<g>
<g transform="rotate(15, 100, 100)" id="svg_17">
<path transform="rotate(180, 99.9999, 74)" id="svg_12"
d="m184.24971,82.10834c0,-20.74388 -75.43989,-37.56014 -168.49966,-37.56014l0,-30.04811l0,0c93.05978,0 168.49966,16.81624 168.49966,37.56014l0,30.04811c0,17.12736 -51.97904,32.08561 -126.37475,36.36745l0,15.02406l-42.12491,-28.85542l42.12491,-31.2408l0,15.02406l0,0c50.16559,-2.88727 91.58326,-10.75847 112.3076,-21.34339"
stroke-width="4" stroke="#ff8800" fill="#ffcc00"/>
<path id="svg_15"
d="m184.24994,134.10839c0,-20.74388 -75.43989,-37.56014 -168.49966,-37.56014l0,-30.04811l0,0c93.05978,0 168.49966,16.81624 168.49966,37.56014l0,30.04811c0,17.12736 -51.97904,32.08561 -126.37475,36.36745l0,15.02406l-42.12491,-28.85542l42.12491,-31.2408l0,15.02406l0,0c50.16559,-2.88727 91.58326,-10.75847 112.3076,-21.34339"
stroke-width="4" stroke="#ff8800" fill="#ffcc00"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -7,16 +7,16 @@
<script>document.getElementsByTagName('base')[0].setAttribute('href', window.location.pathname);</script> <script>document.getElementsByTagName('base')[0].setAttribute('href', window.location.pathname);</script>
<title>Convertorizr - Convert whatever you want!</title> <title>Convertorizr - Convert whatever you want!</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/svg+xml" href="favicon.svg" sizes="any"> <link rel="icon" type="image/x-icon" href="favicon.ico">
</head> </head>
<body> <body>
<h1>Convert it all</h1> <h1>Convert it all</h1>
<noscript class="noscript"> <noscript>This webpage lets you convert data and text to and from various formats. But
This webpage lets you convert data and text to and from various formats. But it requires you to <strong>enable it requires you to <strong>enable Javascript</strong> to do so. So please turn it on in your
Javascript</strong> to do so. So please turn it on in your Browser. You won't regret it! Browser. You won't regret it!
</noscript> </noscript>
<app-root> <app-root>
<div class="app_loader">Please hold on, we're starting the turbines ...</div> <div class="apploader">Please hold on, we're starting the turbines ...</div>
</app-root> </app-root>
</body> </body>
</html> </html>

View File

@ -1,6 +1,11 @@
import { bootstrapApplication } from '@angular/platform-browser'; import {enableProdMode} from '@angular/core';
import { appConfig } from './app/app.config'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, appConfig) import {AppModule} from './app/app.module';
.catch((err) => console.error(err)); import {environment} from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule);

56
src/polyfills.ts Normal file
View File

@ -0,0 +1,56 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
import 'core-js/es6/symbol';
import 'core-js/es6/object';
import 'core-js/es6/function';
import 'core-js/es6/parse-int';
import 'core-js/es6/parse-float';
import 'core-js/es6/number';
import 'core-js/es6/math';
import 'core-js/es6/string';
import 'core-js/es6/date';
import 'core-js/es6/array';
import 'core-js/es6/regexp';
import 'core-js/es6/map';
import 'core-js/es6/set';
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/** IE10 and IE11 requires the following to support `@angular/animation`. */
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/** Evergreen browsers require these. **/
import 'core-js/es6/reflect';
import 'core-js/es7/reflect';
/** ALL Firefox browsers require the following to support `@angular/animation`. **/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/***************************************************************************************************
* Zone JS is required by Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
/**
* Date, currency, decimal and percent pipes.
* Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
*/
// import 'intl'; // Run `npm install --save intl`.

View File

@ -12,25 +12,18 @@
} }
@font-face { @font-face {
font-family: "Liberation Mono"; font-family: "Free Monospaced";
src: url("assets/fonts/liberationmono-regular.ttf") format("truetype"); src: url("assets/fonts/freemono.eot?") format("eot"),
url("assets/fonts/freemono.woff") format("woff"),
url("assets/fonts/freemono.ttf") format("truetype"),
url("assets/fonts/freemono.svg#FreeMono") format("svg");
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
a {
color: #fc0;
transition: all ease-in-out 200ms;
&:hover {
color: #222;
background-color: #fc0;
}
}
body { body {
background-color: #222; background-color: white;
color: #eee; color: black;
font-family: "ABeeZee", sans-serif; font-family: "ABeeZee", sans-serif;
margin: 0; margin: 0;
padding: 1em 0 0 0; padding: 1em 0 0 0;
@ -40,17 +33,6 @@ h1 {
text-align: center; text-align: center;
} }
.noscript { .apploader {
border: 0.1em solid red;
border-radius: 1em;
display: block;
margin: 2em;
font-size: x-large;
padding: 1.5em;
text-align: center;
}
.app_loader {
margin: 2em;
text-align: center; text-align: center;
} }

30
src/test.ts Normal file
View File

@ -0,0 +1,30 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/long-stack-trace-zone';
import 'zone.js/dist/proxy.js';
import 'zone.js/dist/sync-test';
import 'zone.js/dist/jasmine-patch';
import 'zone.js/dist/async-test';
import 'zone.js/dist/fake-async-test';
import {getTestBed} from '@angular/core/testing';
import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing';
// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
declare const __karma__: any;
declare const require: any;
// Prevent Karma from running prematurely.
__karma__.loaded = function () {
};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);
// Finally, start Karma to run the tests.
__karma__.start();

Some files were not shown because too many files have changed in this diff Show More