Merge branch 'master' into rolling_xor
This commit is contained in:
commit
019a68a3a2
1
.github/ISSUE_TEMPLATE.md
vendored
1
.github/ISSUE_TEMPLATE.md
vendored
@ -1 +0,0 @@
|
|||||||
<!-- Prefix the title above with 'Misc:' -->
|
|
||||||
12
.github/ISSUE_TEMPLATE/operation-request.md
vendored
12
.github/ISSUE_TEMPLATE/operation-request.md
vendored
@ -7,8 +7,14 @@ assignees: ''
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Summary
|
**Is your operation request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. E.g. I'm always frustrated when [...]
|
||||||
|
|
||||||
### Example Input
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of the new operation you would like.
|
||||||
|
|
||||||
### Example Output
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Example input and output**
|
||||||
|
Provide an example input to the operation, along with the output that you would expect.
|
||||||
|
|||||||
74
.github/dependabot.yml
vendored
Normal file
74
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# See the documentation for all configuration options:
|
||||||
|
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
#
|
||||||
|
# Check for minor/patch versions only on a weekly basis - we are likely to be able to
|
||||||
|
# merge these routinely. Major versions we'll check for and update manually.
|
||||||
|
#
|
||||||
|
- package-ecosystem: 'npm'
|
||||||
|
directory: '/'
|
||||||
|
versioning-strategy: increase
|
||||||
|
schedule:
|
||||||
|
interval: 'weekly'
|
||||||
|
day: 'friday'
|
||||||
|
time: '03:00'
|
||||||
|
timezone: Europe/London
|
||||||
|
commit-message:
|
||||||
|
prefix: 'chore (deps): '
|
||||||
|
ignore:
|
||||||
|
# we'll do any major version updates manually
|
||||||
|
- dependency-name: '*'
|
||||||
|
update-types: ['version-update:semver-major']
|
||||||
|
# packages we can't currently update
|
||||||
|
# see issue #2214 for rationale for each of these
|
||||||
|
- dependency-name: '@xmldom/xmldom'
|
||||||
|
versions: [ '>=0.9.0' ]
|
||||||
|
- dependency-name: 'bcryptjs'
|
||||||
|
versions: [ '>=3.0.0' ]
|
||||||
|
- dependency-name: 'bootstrap'
|
||||||
|
versions: [ '>=5.0.0' ]
|
||||||
|
- dependency-name: 'bson'
|
||||||
|
versions: [ '>=5.0.0' ]
|
||||||
|
- dependency-name: 'cbor'
|
||||||
|
versions: [ '>=10.0.0' ]
|
||||||
|
- dependency-name: 'cspell'
|
||||||
|
versions: [ '>=9.0.0' ]
|
||||||
|
- dependency-name: 'eslint'
|
||||||
|
versions: [ '>=10.0.0' ]
|
||||||
|
- dependency-name: 'eslint-plugin-jsdoc'
|
||||||
|
versions: [ '>=51.0.0' ]
|
||||||
|
- dependency-name: 'fernet'
|
||||||
|
versions: [ '>=0.4.0' ]
|
||||||
|
- dependency-name: 'geodesy'
|
||||||
|
versions: [ '>=2.0.0' ]
|
||||||
|
- dependency-name: 'jimp'
|
||||||
|
versions: [ '1.6.1' ]
|
||||||
|
- dependency-name: 'otpauth'
|
||||||
|
versions: [ '>=9.4.0' ]
|
||||||
|
- dependency-name: 'webpack-dev-server'
|
||||||
|
versions: [ '>=5.1.0' ]
|
||||||
|
groups:
|
||||||
|
#
|
||||||
|
# Grouping so we don't get a seperate PR for every patch version.
|
||||||
|
#
|
||||||
|
patch-updates:
|
||||||
|
applies-to: version-updates
|
||||||
|
patterns:
|
||||||
|
- '*'
|
||||||
|
update-types:
|
||||||
|
- 'patch'
|
||||||
|
|
||||||
|
# Can't enable this until we are using Node 24 as the latest actions all require this version
|
||||||
|
# - package-ecosystem: "github-actions"
|
||||||
|
# # Workflow files stored in the default location of `.github/workflows`; no need to
|
||||||
|
# # specify `/.github/workflows` for `directory`
|
||||||
|
# directory: '/'
|
||||||
|
# schedule:
|
||||||
|
# interval: 'weekly'
|
||||||
|
# day: 'friday'
|
||||||
|
# time: '03:00'
|
||||||
|
# timezone: Europe/London
|
||||||
|
# commit-message:
|
||||||
|
# prefix: 'chore (deps): '
|
||||||
15
.github/pull_request_template.md
vendored
Normal file
15
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
**Description**
|
||||||
|
Provide a description of the pull request and the changes that it makes.
|
||||||
|
|
||||||
|
**Existing Issue**
|
||||||
|
If this pull request relates to an existing issue in the repository, please link it here.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If the pull request changes any visual aspects of CyberChef, please include screenshots.
|
||||||
|
|
||||||
|
**AI disclosure**
|
||||||
|
If you have used any AI tools while creating this code, **you must declare your usage along with the name of the tools that you used**.
|
||||||
|
Regardless of AI tool usage, you are responsible for any code that you submit, and we expect you to have checked the code and have enough of an understanding of it to answer any questions we might have.
|
||||||
|
|
||||||
|
**Test Coverage**
|
||||||
|
Please ensure you have added test coverage for your changes.
|
||||||
10
.github/workflows/master.yml
vendored
10
.github/workflows/master.yml
vendored
@ -1,18 +1,19 @@
|
|||||||
name: "Master Build, Test & Deploy"
|
name: "Master Build, Test & Deploy"
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
main:
|
main:
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
pages: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
@ -25,8 +26,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
run: |
|
run: |
|
||||||
export DETECT_CHROMEDRIVER_VERSION=true
|
npm ci
|
||||||
npm install
|
|
||||||
npm run setheapsize
|
npm run setheapsize
|
||||||
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
|
|||||||
23
.github/workflows/pull_requests.yml
vendored
23
.github/workflows/pull_requests.yml
vendored
@ -22,8 +22,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
run: |
|
run: |
|
||||||
export DETECT_CHROMEDRIVER_VERSION=true
|
npm ci
|
||||||
npm install
|
|
||||||
npm run setheapsize
|
npm run setheapsize
|
||||||
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
@ -38,7 +37,22 @@ jobs:
|
|||||||
if: success()
|
if: success()
|
||||||
run: npx grunt prod
|
run: npx grunt prod
|
||||||
|
|
||||||
|
- name: Upload Build Artefact
|
||||||
|
if: success()
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: zipped-build
|
||||||
|
path: build/prod/*.zip
|
||||||
|
retention-days: 5
|
||||||
|
|
||||||
|
- name: UI Tests
|
||||||
|
if: success()
|
||||||
|
run: |
|
||||||
|
sudo apt-get install xvfb
|
||||||
|
xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
|
if: success()
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
@ -50,8 +64,3 @@ jobs:
|
|||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
- name: UI Tests
|
|
||||||
if: success()
|
|
||||||
run: |
|
|
||||||
sudo apt-get install xvfb
|
|
||||||
xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
|
|
||||||
|
|||||||
32
.github/workflows/releases.yml
vendored
32
.github/workflows/releases.yml
vendored
@ -7,7 +7,6 @@ on:
|
|||||||
- "v*"
|
- "v*"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
env:
|
env:
|
||||||
@ -18,6 +17,9 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
main:
|
main:
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
contents: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
@ -30,7 +32,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
run: |
|
run: |
|
||||||
export DETECT_CHROMEDRIVER_VERSION=true
|
|
||||||
npm ci
|
npm ci
|
||||||
npm run setheapsize
|
npm run setheapsize
|
||||||
|
|
||||||
@ -93,5 +94,32 @@ jobs:
|
|||||||
file_glob: true
|
file_glob: true
|
||||||
body: "See the [CHANGELOG](https://github.com/gchq/CyberChef/blob/master/CHANGELOG.md) and [commit messages](https://github.com/gchq/CyberChef/commits/master) for details."
|
body: "See the [CHANGELOG](https://github.com/gchq/CyberChef/blob/master/CHANGELOG.md) and [commit messages](https://github.com/gchq/CyberChef/commits/master) for details."
|
||||||
|
|
||||||
|
npm-publish:
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
needs: main
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Set node version
|
||||||
|
uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
registry-url: "https://registry.npmjs.org"
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Create machine generated files
|
||||||
|
run: npm run node
|
||||||
|
|
||||||
|
- name: Reset node version ready for publish
|
||||||
|
uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: ^24.5
|
||||||
|
registry-url: "https://registry.npmjs.org"
|
||||||
|
|
||||||
- name: Publish to NPM
|
- name: Publish to NPM
|
||||||
run: npm publish
|
run: npm publish
|
||||||
|
|||||||
200
CHANGELOG.md
200
CHANGELOG.md
@ -13,6 +13,93 @@ All major and minor version changes will be documented in this file. Details of
|
|||||||
|
|
||||||
## Details
|
## Details
|
||||||
|
|
||||||
|
### [10.23.0] - 2026-04-06
|
||||||
|
- Properly escape HTML entities in sampleDelim to avoid XSS issue [@GCHQDeveloper581] | [#2307]
|
||||||
|
- chore (deps): bump lodash from 4.17.23 to 4.18.1 | [#2304]
|
||||||
|
- chore (deps): bump @codemirror/view from 6.40.0 to 6.41.0 | [#2305]
|
||||||
|
- chore (deps): bump the patch-updates group with 2 updates | [#2303]
|
||||||
|
- chore (deps): bump @xmldom/xmldom from 0.8.11 to 0.8.12 | [#2302]
|
||||||
|
- chore (deps): bump picomatch | [#2299]
|
||||||
|
- chore (deps): bump node-forge from 1.3.3 to 1.4.0 | [#2297]
|
||||||
|
- chore (deps): bump the patch-updates group with 3 updates | [#2296]
|
||||||
|
- chore (deps) bump chromedriver from 130.0.4 to 146.0.6 [@GCHQDeveloper581] | [#2292]
|
||||||
|
- ParseEthernetFrame - Fix vlan calculation [@Kalkran] | [#2295]
|
||||||
|
- Add pull request template with AI usage disclosure [@C85297] | [#2279]
|
||||||
|
- fix: return empty output for zero-length To Modhex input [@saschabuehrle] | [#2249]
|
||||||
|
- Added tab focus to top banner and navigation to About/Support Modal [@j264415] | [#1733]
|
||||||
|
- Add Parse Ethernet frame Operation, allow Parse IPv4 Header to cascade [@Kalkran] | [#1722]
|
||||||
|
- Selection and Deselection of autobake checkbox using keyboard [@j264415] | [#1727]
|
||||||
|
- chore (deps): bump @babel/runtime from 7.28.6 to 7.29.2 | [#2263]
|
||||||
|
- Add more helpful error for when numerical ingredient is left empty [@Lamby777] [@C85297] | [#1540]
|
||||||
|
- chore (deps): bump @codemirror/view from 6.39.17 to 6.40.0 | [#2262]
|
||||||
|
- Bump flatted from 3.3.2 to 3.4.2 [@GCHQDeveloper581] | [#2266]
|
||||||
|
- feat: add Raw option for Jq operation [@rtpt-romankarwacik] | [#2237]
|
||||||
|
- chore (deps): bump core-js from 3.48.0 to 3.49.0 | [#2261]
|
||||||
|
- chore (deps): bump the patch-updates group with 6 updates | [#2260]
|
||||||
|
- Add Extract Audio Metadata operation [@d0s1nt] [@GCHQDeveloper581] | [#2170]
|
||||||
|
- Fix Jq issue [@GCHQDeveloper581] | [#2210]
|
||||||
|
- Configure dependabot updates [@GCHQDeveloper581] | [#2259]
|
||||||
|
- fix(A1Z26): return empty string instead of empty array for empty input [@brick-pixel] | [#2257]
|
||||||
|
- Fix broken Docker link in README [@am-periphery] | [#2250]
|
||||||
|
- Update some dependencies, including a number causing npm audit warnings [@GCHQDeveloper581] | [#2236]
|
||||||
|
- Bump axios from 1.7.9 to 1.13.6 | [#2234]
|
||||||
|
- Bump jws from 3.2.2 to 3.2.3 | [#2235]
|
||||||
|
- Bump pbkdf2 from 3.1.2 to 3.1.5 | [#2229]
|
||||||
|
- Bump form-data from 4.0.1 to 4.0.5 | [#2228]
|
||||||
|
- Bump basic-ftp from 5.0.5 to 5.2.0 | [#2231]
|
||||||
|
- feat: add ARM disassembler operation [@thomasxm] | [#2156]
|
||||||
|
- Add Text/Integer Converter operation [@p-leriche] [@GCHQDeveloper581] | [#2213]
|
||||||
|
- Feat/rc6 add RC6 Encrypt/Decrypt operations [@thomasxm] | [#2163]
|
||||||
|
- [bugfix] Add Bootstrap form style for CodeMirror editor [@Swonkie] | [#2161]
|
||||||
|
- Add Flask Session operations (Decode, Sign, Verify) [@ThePlayer372-FR] | [#2208]
|
||||||
|
- fix: `jq-web` -> `jq-wasm`, includes `jq` version `1.8.1` [@W-Floyd] [@GCHQDeveloper581] | [#2223]
|
||||||
|
- Bump jsonwebtoken from 8.5.1 to 9.0.0 [@GCHQDeveloper581] | [#2219]
|
||||||
|
- Bump basic-ftp from 5.0.5 to 5.2.0 | [#2218]
|
||||||
|
- feat: add random integer generation operation [@cktgh] | [#2151]
|
||||||
|
- Add BigInt utility functions for number theory operations [@p-leriche] [@GCHQDeveloper581] | [#2205]
|
||||||
|
- Improve SQL Beautify: use sql-formatter and support bind variables [@aby-jo] [@GCHQDeveloper581] | [#2071]
|
||||||
|
- update tesseract.js to 6.0.1 [@atsiv1] | [#2133]
|
||||||
|
- Fix hint tooltip display issues [@bartvanandel] | [#2017]
|
||||||
|
- Simplify babel dependencies [@GCHQDeveloper581] | [#2204]
|
||||||
|
- Dependency updates [@GCHQDeveloper581] | [#2201]
|
||||||
|
- Fix: Move Magic checks from Escape to Unescape Unicode Characters [@fjh1997] | [#2195]
|
||||||
|
- Paste spreadsheets as text [@C85297] | [#2200]
|
||||||
|
- Fix Roboto Mono font [@C85297] | [#2199]
|
||||||
|
- Fix return of buffer for PNG QR image generation [@GCHQDeveloper581] [@C85297] | [#2125]
|
||||||
|
- Update JIMP [@C85297] | [#2171]
|
||||||
|
- Overwrite NGINX maintainer label [@C85297] | [#2194]
|
||||||
|
- Bump v10.22.1 [@GCHQDeveloper581] | [#2193]
|
||||||
|
- Fix npm publish - Run "npm ci" and "npm run node" under node 18 then switch to node 24.5 [@GCHQDeveloper581] | [#2192]
|
||||||
|
|
||||||
|
### [10.22.0] - 2026-02-11
|
||||||
|
- Separate npm publish out into separate job and run with Node 24.5 [@GCHQDeveloper581] | [#2188]
|
||||||
|
- Fixed Percent delimiter for hex encoding [@beneri] [@C85297] | [#2137]
|
||||||
|
- Added the ability to paste one or more Images from the Clipboard [@t-martine] [@a3957273] [@C85297] | [#1876]
|
||||||
|
- Quoted Printable - consistent reference to 'email' [@wesinator] | [#2186]
|
||||||
|
- Fix freeze when output text decoding fails [@Raka-loah] | [#1573]
|
||||||
|
- Update Browserslist DB [@C85297] | [#2183]
|
||||||
|
- Add contents write permission to releases workflow [@C85297] | [#2182]
|
||||||
|
- Fix release workflow permissions [@C85297] | [#2181]
|
||||||
|
|
||||||
|
### [10.21.0] - 2026-02-05
|
||||||
|
- Fix import operations with special chars in them [@d98762625] [@jg42526] | [#1040]
|
||||||
|
- Remove custom CodeQL workflow [@C85297] | [#2176]
|
||||||
|
- Fix code scanning warnings in workflows [@GCHQDeveloper581] | [#2177]
|
||||||
|
- Use NPM trusted publishing [@C85297] [@GCHQDeveloper581] | [#2174]
|
||||||
|
- Fix: Correctly parse xxd odd byte hexdumps [@ThomasNotTom] [@GCHQDeveloper581] | [#2058]
|
||||||
|
- Update Sitemap URLs to Use Valid Paths in sitemap.mjs [@rbpi] [@C85297] | [#1861]
|
||||||
|
- Use recommended GitHub Actions to build image [@AlexGustafsson] [@C85297] | [#2055]
|
||||||
|
- Remove version 10 message from banner [@C85297] | [#2169]
|
||||||
|
- Bump form-data from 4.0.1 to 4.0.5 | [#2175]
|
||||||
|
- Bump node-forge from 1.3.1 to 1.3.3 | [#2173]
|
||||||
|
- Update crypto browserify [@C85297] | [#2172]
|
||||||
|
- Update kbpgp package (resolves #2135) [@GCHQDeveloper581] | [#2136]
|
||||||
|
- Fix the processing of ALPNs for JA4 to align with new specification update [@tuliperis] | [#2165]
|
||||||
|
- Add Bech32 and Bech32m encoding/decoding operations [@thomasxm] | [#2159]
|
||||||
|
- Exclude Delete character from hex dump output [@mikecat] [@C85297] | [#2086]
|
||||||
|
- Tiny typo fix in "To Base85" operation [@twostraws] | [#2118]
|
||||||
|
- Bump jsonpath-plus [@C85297] | [#2166]
|
||||||
|
|
||||||
### [10.20.0] - 2026-01-28
|
### [10.20.0] - 2026-01-28
|
||||||
- Fixed Optical Character Recognition and added tests [@n1474335] | [ab37c1e]
|
- Fixed Optical Character Recognition and added tests [@n1474335] | [ab37c1e]
|
||||||
- Fixed JA4 version fallback value [@n1474335] | [7a5225c]
|
- Fixed JA4 version fallback value [@n1474335] | [7a5225c]
|
||||||
@ -509,6 +596,9 @@ All major and minor version changes will be documented in this file. Details of
|
|||||||
## [4.0.0] - 2016-11-28
|
## [4.0.0] - 2016-11-28
|
||||||
- Initial open source commit [@n1474335] | [b1d73a72](https://github.com/gchq/CyberChef/commit/b1d73a725dc7ab9fb7eb789296efd2b7e4b08306)
|
- Initial open source commit [@n1474335] | [b1d73a72](https://github.com/gchq/CyberChef/commit/b1d73a725dc7ab9fb7eb789296efd2b7e4b08306)
|
||||||
|
|
||||||
|
[10.23.0]: https://github.com/gchq/CyberChef/releases/tag/v10.23.0
|
||||||
|
[10.22.0]: https://github.com/gchq/CyberChef/releases/tag/v10.22.0
|
||||||
|
[10.21.0]: https://github.com/gchq/CyberChef/releases/tag/v10.21.0
|
||||||
[10.20.0]: https://github.com/gchq/CyberChef/releases/tag/v10.20.0
|
[10.20.0]: https://github.com/gchq/CyberChef/releases/tag/v10.20.0
|
||||||
[10.19.0]: https://github.com/gchq/CyberChef/releases/tag/v10.19.0
|
[10.19.0]: https://github.com/gchq/CyberChef/releases/tag/v10.19.0
|
||||||
[10.18.0]: https://github.com/gchq/CyberChef/releases/tag/v10.18.0
|
[10.18.0]: https://github.com/gchq/CyberChef/releases/tag/v10.18.0
|
||||||
@ -754,6 +844,34 @@ All major and minor version changes will be documented in this file. Details of
|
|||||||
[@remingtr]: https://github.com/remingtr
|
[@remingtr]: https://github.com/remingtr
|
||||||
[@0xff1ce]: https://github.com/0xff1ce
|
[@0xff1ce]: https://github.com/0xff1ce
|
||||||
[@starplanet]: https://github.com/starplanet
|
[@starplanet]: https://github.com/starplanet
|
||||||
|
[@C85297]: https://github.com/C85297
|
||||||
|
[@GCHQDeveloper581]: https://github.com/GCHQDeveloper581
|
||||||
|
[@ThomasNotTom]: https://github.com/ThomasNotTom
|
||||||
|
[@rbpi]: https://github.com/rbpi
|
||||||
|
[@AlexGustafsson]: https://github.com/AlexGustafsson
|
||||||
|
[@tuliperis]: https://github.com/tuliperis
|
||||||
|
[@thomasxm]: https://github.com/thomasxm
|
||||||
|
[@twostraws]: https://github.com/twostraws
|
||||||
|
[@beneri]: https://github.com/beneri
|
||||||
|
[@t-martine]: https://github.com/t-martine
|
||||||
|
[@wesinator]: https://github.com/wesinator
|
||||||
|
[@Raka-loah]: https://github.com/Raka-loah
|
||||||
|
[@Kalkran]: https://github.com/Kalkran
|
||||||
|
[@saschabuehrle]: https://github.com/saschabuehrle
|
||||||
|
[@j264415]: https://github.com/j264415
|
||||||
|
[@Lamby777]: https://github.com/Lamby777
|
||||||
|
[@rtpt-romankarwacik]: https://github.com/rtpt-romankarwacik
|
||||||
|
[@d0s1nt]: https://github.com/d0s1nt
|
||||||
|
[@brick-pixel]: https://github.com/brick-pixel
|
||||||
|
[@am-periphery]: https://github.com/am-periphery
|
||||||
|
[@p-leriche]: https://github.com/p-leriche
|
||||||
|
[@Swonkie]: https://github.com/Swonkie
|
||||||
|
[@ThePlayer372-FR]: https://github.com/ThePlayer372-FR
|
||||||
|
[@W-Floyd]: https://github.com/W-Floyd
|
||||||
|
[@cktgh]: https://github.com/cktgh
|
||||||
|
[@aby-jo]: https://github.com/aby-jo
|
||||||
|
[@atsiv1]: https://github.com/atsiv1
|
||||||
|
[@fjh1997]: https://github.com/fjh1997
|
||||||
|
|
||||||
|
|
||||||
[8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7
|
[8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7
|
||||||
@ -942,3 +1060,85 @@ All major and minor version changes will be documented in this file. Details of
|
|||||||
[#512]: https://github.com/gchq/CyberChef/issues/512
|
[#512]: https://github.com/gchq/CyberChef/issues/512
|
||||||
[#1732]: https://github.com/gchq/CyberChef/issues/1732
|
[#1732]: https://github.com/gchq/CyberChef/issues/1732
|
||||||
[#1789]: https://github.com/gchq/CyberChef/issues/1789
|
[#1789]: https://github.com/gchq/CyberChef/issues/1789
|
||||||
|
[#1040]: https://github.com/gchq/CyberChef/pull/1040
|
||||||
|
[#2176]: https://github.com/gchq/CyberChef/pull/2176
|
||||||
|
[#2177]: https://github.com/gchq/CyberChef/pull/2177
|
||||||
|
[#2174]: https://github.com/gchq/CyberChef/pull/2174
|
||||||
|
[#2058]: https://github.com/gchq/CyberChef/pull/2058
|
||||||
|
[#1861]: https://github.com/gchq/CyberChef/pull/1861
|
||||||
|
[#2055]: https://github.com/gchq/CyberChef/pull/2055
|
||||||
|
[#2169]: https://github.com/gchq/CyberChef/pull/2169
|
||||||
|
[#2175]: https://github.com/gchq/CyberChef/pull/2175
|
||||||
|
[#2173]: https://github.com/gchq/CyberChef/pull/2173
|
||||||
|
[#2172]: https://github.com/gchq/CyberChef/pull/2172
|
||||||
|
[#2136]: https://github.com/gchq/CyberChef/pull/2136
|
||||||
|
[#2165]: https://github.com/gchq/CyberChef/pull/2165
|
||||||
|
[#2159]: https://github.com/gchq/CyberChef/pull/2159
|
||||||
|
[#2086]: https://github.com/gchq/CyberChef/pull/2086
|
||||||
|
[#2118]: https://github.com/gchq/CyberChef/pull/2118
|
||||||
|
[#2166]: https://github.com/gchq/CyberChef/pull/2166
|
||||||
|
[#2188]: https://github.com/gchq/CyberChef/pull/2188
|
||||||
|
[#2137]: https://github.com/gchq/CyberChef/pull/2137
|
||||||
|
[#1876]: https://github.com/gchq/CyberChef/pull/1876
|
||||||
|
[#2186]: https://github.com/gchq/CyberChef/pull/2186
|
||||||
|
[#1573]: https://github.com/gchq/CyberChef/pull/1573
|
||||||
|
[#2183]: https://github.com/gchq/CyberChef/pull/2183
|
||||||
|
[#2182]: https://github.com/gchq/CyberChef/pull/2182
|
||||||
|
[#2181]: https://github.com/gchq/CyberChef/pull/2181
|
||||||
|
[#2307]: https://github.com/gchq/CyberChef/pull/2307
|
||||||
|
[#2304]: https://github.com/gchq/CyberChef/pull/2304
|
||||||
|
[#2305]: https://github.com/gchq/CyberChef/pull/2305
|
||||||
|
[#2303]: https://github.com/gchq/CyberChef/pull/2303
|
||||||
|
[#2302]: https://github.com/gchq/CyberChef/pull/2302
|
||||||
|
[#2299]: https://github.com/gchq/CyberChef/pull/2299
|
||||||
|
[#2297]: https://github.com/gchq/CyberChef/pull/2297
|
||||||
|
[#2296]: https://github.com/gchq/CyberChef/pull/2296
|
||||||
|
[#2292]: https://github.com/gchq/CyberChef/pull/2292
|
||||||
|
[#2295]: https://github.com/gchq/CyberChef/pull/2295
|
||||||
|
[#2279]: https://github.com/gchq/CyberChef/pull/2279
|
||||||
|
[#2249]: https://github.com/gchq/CyberChef/pull/2249
|
||||||
|
[#1733]: https://github.com/gchq/CyberChef/pull/1733
|
||||||
|
[#1722]: https://github.com/gchq/CyberChef/pull/1722
|
||||||
|
[#1727]: https://github.com/gchq/CyberChef/pull/1727
|
||||||
|
[#2263]: https://github.com/gchq/CyberChef/pull/2263
|
||||||
|
[#1540]: https://github.com/gchq/CyberChef/pull/1540
|
||||||
|
[#2262]: https://github.com/gchq/CyberChef/pull/2262
|
||||||
|
[#2266]: https://github.com/gchq/CyberChef/pull/2266
|
||||||
|
[#2237]: https://github.com/gchq/CyberChef/pull/2237
|
||||||
|
[#2261]: https://github.com/gchq/CyberChef/pull/2261
|
||||||
|
[#2260]: https://github.com/gchq/CyberChef/pull/2260
|
||||||
|
[#2170]: https://github.com/gchq/CyberChef/pull/2170
|
||||||
|
[#2210]: https://github.com/gchq/CyberChef/pull/2210
|
||||||
|
[#2259]: https://github.com/gchq/CyberChef/pull/2259
|
||||||
|
[#2257]: https://github.com/gchq/CyberChef/pull/2257
|
||||||
|
[#2250]: https://github.com/gchq/CyberChef/pull/2250
|
||||||
|
[#2236]: https://github.com/gchq/CyberChef/pull/2236
|
||||||
|
[#2234]: https://github.com/gchq/CyberChef/pull/2234
|
||||||
|
[#2235]: https://github.com/gchq/CyberChef/pull/2235
|
||||||
|
[#2229]: https://github.com/gchq/CyberChef/pull/2229
|
||||||
|
[#2228]: https://github.com/gchq/CyberChef/pull/2228
|
||||||
|
[#2231]: https://github.com/gchq/CyberChef/pull/2231
|
||||||
|
[#2156]: https://github.com/gchq/CyberChef/pull/2156
|
||||||
|
[#2213]: https://github.com/gchq/CyberChef/pull/2213
|
||||||
|
[#2163]: https://github.com/gchq/CyberChef/pull/2163
|
||||||
|
[#2161]: https://github.com/gchq/CyberChef/pull/2161
|
||||||
|
[#2208]: https://github.com/gchq/CyberChef/pull/2208
|
||||||
|
[#2223]: https://github.com/gchq/CyberChef/pull/2223
|
||||||
|
[#2219]: https://github.com/gchq/CyberChef/pull/2219
|
||||||
|
[#2218]: https://github.com/gchq/CyberChef/pull/2218
|
||||||
|
[#2151]: https://github.com/gchq/CyberChef/pull/2151
|
||||||
|
[#2205]: https://github.com/gchq/CyberChef/pull/2205
|
||||||
|
[#2071]: https://github.com/gchq/CyberChef/pull/2071
|
||||||
|
[#2133]: https://github.com/gchq/CyberChef/pull/2133
|
||||||
|
[#2017]: https://github.com/gchq/CyberChef/pull/2017
|
||||||
|
[#2204]: https://github.com/gchq/CyberChef/pull/2204
|
||||||
|
[#2201]: https://github.com/gchq/CyberChef/pull/2201
|
||||||
|
[#2195]: https://github.com/gchq/CyberChef/pull/2195
|
||||||
|
[#2200]: https://github.com/gchq/CyberChef/pull/2200
|
||||||
|
[#2199]: https://github.com/gchq/CyberChef/pull/2199
|
||||||
|
[#2125]: https://github.com/gchq/CyberChef/pull/2125
|
||||||
|
[#2171]: https://github.com/gchq/CyberChef/pull/2171
|
||||||
|
[#2194]: https://github.com/gchq/CyberChef/pull/2194
|
||||||
|
[#2193]: https://github.com/gchq/CyberChef/pull/2193
|
||||||
|
[#2192]: https://github.com/gchq/CyberChef/pull/2192
|
||||||
|
|
||||||
|
|||||||
@ -29,4 +29,6 @@ RUN npm run build
|
|||||||
#########################################
|
#########################################
|
||||||
FROM nginx:stable-alpine AS cyberchef
|
FROM nginx:stable-alpine AS cyberchef
|
||||||
|
|
||||||
|
LABEL maintainer="GCHQ <oss@gchq.gov.uk>"
|
||||||
|
|
||||||
COPY --from=builder /app/build/prod /usr/share/nginx/html/
|
COPY --from=builder /app/build/prod /usr/share/nginx/html/
|
||||||
|
|||||||
30
Gruntfile.js
30
Gruntfile.js
@ -411,39 +411,13 @@ module.exports = function (grunt) {
|
|||||||
stdout: false,
|
stdout: false,
|
||||||
},
|
},
|
||||||
fixCryptoApiImports: {
|
fixCryptoApiImports: {
|
||||||
command: function () {
|
command: `node ${nodeFlags} src/core/config/scripts/fixCryptoApiImports.mjs`,
|
||||||
switch (process.platform) {
|
|
||||||
case "darwin":
|
|
||||||
return `find ./node_modules/crypto-api/src/ \\( -type d -name .git -prune \\) -o -type f -print0 | xargs -0 sed -i '' -e '/\\.mjs/!s/\\(from "\\.[^"]*\\)";/\\1.mjs";/g'`;
|
|
||||||
default:
|
|
||||||
return `find ./node_modules/crypto-api/src/ \\( -type d -name .git -prune \\) -o -type f -print0 | xargs -0 sed -i -e '/\\.mjs/!s/\\(from "\\.[^"]*\\)";/\\1.mjs";/g'`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stdout: false
|
stdout: false
|
||||||
},
|
},
|
||||||
fixSnackbarMarkup: {
|
fixSnackbarMarkup: {
|
||||||
command: function () {
|
command: `node ${nodeFlags} src/core/config/scripts/fixSnackBarMarkup.mjs`,
|
||||||
switch (process.platform) {
|
|
||||||
case "darwin":
|
|
||||||
return `sed -i '' 's/<div id=snackbar-container\\/>/<div id=snackbar-container>/g' ./node_modules/snackbarjs/src/snackbar.js`;
|
|
||||||
default:
|
|
||||||
return `sed -i 's/<div id=snackbar-container\\/>/<div id=snackbar-container>/g' ./node_modules/snackbarjs/src/snackbar.js`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stdout: false
|
stdout: false
|
||||||
},
|
},
|
||||||
fixJimpModule: {
|
|
||||||
command: function () {
|
|
||||||
switch (process.platform) {
|
|
||||||
case "darwin":
|
|
||||||
// Space added before comma to prevent multiple modifications
|
|
||||||
return `sed -i '' 's/"es\\/index.js",/"es\\/index.js" ,\\n "type": "module",/' ./node_modules/jimp/package.json`;
|
|
||||||
default:
|
|
||||||
return `sed -i 's/"es\\/index.js",/"es\\/index.js" ,\\n "type": "module",/' ./node_modules/jimp/package.json`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stdout: false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -24,7 +24,7 @@ Cryptographic operations in CyberChef should not be relied upon to provide secur
|
|||||||
|
|
||||||
**Prerequisites**
|
**Prerequisites**
|
||||||
|
|
||||||
- [Docker](hhttps://www.docker.com/products/docker-desktop/)
|
- [Docker](https://www.docker.com/products/docker-desktop/)
|
||||||
- Docker Desktop must be open and running on your machine
|
- Docker Desktop must be open and running on your machine
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -10,13 +10,7 @@ module.exports = function(api) {
|
|||||||
}]
|
}]
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"dynamic-import-node",
|
|
||||||
"@babel/plugin-syntax-import-assertions",
|
"@babel/plugin-syntax-import-assertions",
|
||||||
[
|
|
||||||
"babel-plugin-transform-builtin-extend", {
|
|
||||||
"globals": ["Error"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
"@babel/plugin-transform-runtime", {
|
"@babel/plugin-transform-runtime", {
|
||||||
"regenerator": true
|
"regenerator": true
|
||||||
|
|||||||
5364
package-lock.json
generated
5364
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
130
package.json
130
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cyberchef",
|
"name": "cyberchef",
|
||||||
"version": "10.20.0",
|
"version": "10.23.0",
|
||||||
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
|
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
|
||||||
"author": "n1474335 <n1474335@gmail.com>",
|
"author": "n1474335 <n1474335@gmail.com>",
|
||||||
"homepage": "https://gchq.github.io/CyberChef",
|
"homepage": "https://gchq.github.io/CyberChef",
|
||||||
@ -39,73 +39,71 @@
|
|||||||
"node >= 16"
|
"node >= 16"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.24.7",
|
"@babel/eslint-parser": "^7.28.6",
|
||||||
"@babel/eslint-parser": "^7.24.7",
|
"@babel/plugin-syntax-import-assertions": "^7.28.6",
|
||||||
"@babel/plugin-syntax-import-assertions": "^7.24.7",
|
"@babel/plugin-transform-runtime": "^7.29.0",
|
||||||
"@babel/plugin-transform-runtime": "^7.24.7",
|
"@babel/preset-env": "^7.29.2",
|
||||||
"@babel/preset-env": "^7.24.7",
|
"@babel/runtime": "^7.29.2",
|
||||||
"@babel/runtime": "^7.24.7",
|
"@codemirror/commands": "^6.10.3",
|
||||||
"@codemirror/commands": "^6.6.0",
|
"@codemirror/language": "^6.12.3",
|
||||||
"@codemirror/language": "^6.10.2",
|
"@codemirror/search": "^6.7.0",
|
||||||
"@codemirror/search": "^6.5.6",
|
"@codemirror/state": "^6.5.4",
|
||||||
"@codemirror/state": "^6.4.1",
|
"@codemirror/view": "^6.41.1",
|
||||||
"@codemirror/view": "^6.28.0",
|
"autoprefixer": "^10.5.0",
|
||||||
"autoprefixer": "^10.4.19",
|
"babel-loader": "^10.1.1",
|
||||||
"babel-loader": "^9.1.3",
|
|
||||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
|
||||||
"babel-plugin-transform-builtin-extend": "1.1.2",
|
|
||||||
"base64-loader": "^1.0.0",
|
"base64-loader": "^1.0.0",
|
||||||
"chromedriver": "^130.0.0",
|
"chromedriver": "^146.0.6",
|
||||||
"cli-progress": "^3.12.0",
|
"cli-progress": "^3.12.0",
|
||||||
"colors": "^1.4.0",
|
"colors": "^1.4.0",
|
||||||
"compression-webpack-plugin": "^11.1.0",
|
"compression-webpack-plugin": "^11.1.0",
|
||||||
"copy-webpack-plugin": "^12.0.2",
|
"copy-webpack-plugin": "^13.0.1",
|
||||||
"core-js": "^3.37.1",
|
"core-js": "^3.49.0",
|
||||||
"cspell": "^8.17.3",
|
"cspell": "^8.19.4",
|
||||||
"css-loader": "7.1.2",
|
"css-loader": "7.1.4",
|
||||||
"eslint": "^9.4.0",
|
"eslint": "^9.39.4",
|
||||||
"eslint-plugin-jsdoc": "^48.2.9",
|
"eslint-plugin-jsdoc": "^50.8.0",
|
||||||
"globals": "^15.4.0",
|
"globals": "^15.15.0",
|
||||||
"grunt": "^1.6.1",
|
"grunt": "^1.6.2",
|
||||||
"grunt-chmod": "~1.1.1",
|
"grunt-chmod": "~1.1.1",
|
||||||
"grunt-concurrent": "^3.0.0",
|
"grunt-concurrent": "^3.0.0",
|
||||||
"grunt-contrib-clean": "~2.0.1",
|
"grunt-contrib-clean": "~2.0.1",
|
||||||
"grunt-contrib-connect": "^4.0.0",
|
"grunt-contrib-connect": "^5.0.1",
|
||||||
"grunt-contrib-copy": "~1.0.0",
|
"grunt-contrib-copy": "~1.0.0",
|
||||||
"grunt-contrib-watch": "^1.1.0",
|
"grunt-contrib-watch": "^1.1.0",
|
||||||
"grunt-eslint": "^25.0.0",
|
"grunt-eslint": "^25.0.0",
|
||||||
"grunt-exec": "~3.0.0",
|
"grunt-exec": "~3.0.0",
|
||||||
"grunt-webpack": "^6.0.0",
|
"grunt-webpack": "^6.0.0",
|
||||||
"grunt-zip": "^1.0.0",
|
"grunt-zip": "^1.0.0",
|
||||||
"html-webpack-plugin": "^5.6.0",
|
"html-webpack-plugin": "^5.6.7",
|
||||||
"imports-loader": "^5.0.0",
|
"imports-loader": "^5.0.0",
|
||||||
"mini-css-extract-plugin": "2.9.0",
|
"mini-css-extract-plugin": "2.10.2",
|
||||||
"modify-source-webpack-plugin": "^4.1.0",
|
"modify-source-webpack-plugin": "^4.1.0",
|
||||||
"nightwatch": "^3.6.3",
|
"nightwatch": "^3.15.0",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.5.10",
|
||||||
"postcss-css-variables": "^0.19.0",
|
"postcss-css-variables": "^0.19.0",
|
||||||
"postcss-import": "^16.1.0",
|
"postcss-import": "^16.1.1",
|
||||||
"postcss-loader": "^8.1.1",
|
"postcss-loader": "^8.2.1",
|
||||||
"prompt": "^1.3.0",
|
"prompt": "^1.3.0",
|
||||||
"sitemap": "^8.0.0",
|
"sitemap": "^8.0.3",
|
||||||
"terser": "^5.31.1",
|
"terser": "^5.46.2",
|
||||||
"webpack": "^5.91.0",
|
"webpack": "^5.106.2",
|
||||||
"webpack-bundle-analyzer": "^4.10.2",
|
"webpack-bundle-analyzer": "^4.10.2",
|
||||||
"webpack-dev-server": "5.0.4",
|
"webpack-dev-server": "5.0.4",
|
||||||
"webpack-node-externals": "^3.0.0",
|
"webpack-node-externals": "^3.0.0",
|
||||||
"worker-loader": "^3.0.8"
|
"worker-loader": "^3.0.8"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@alexaltea/capstone-js": "^3.0.5",
|
||||||
"@astronautlabs/amf": "^0.0.6",
|
"@astronautlabs/amf": "^0.0.6",
|
||||||
"@babel/polyfill": "^7.12.1",
|
|
||||||
"@blu3r4y/lzma": "^2.3.3",
|
"@blu3r4y/lzma": "^2.3.3",
|
||||||
"@wavesenterprise/crypto-gost-js": "^2.1.0-RC1",
|
"@wavesenterprise/crypto-gost-js": "^2.1.0-RC1",
|
||||||
"@xmldom/xmldom": "^0.8.10",
|
"@xmldom/xmldom": "^0.8.13",
|
||||||
"argon2-browser": "^1.18.0",
|
"argon2-browser": "^1.18.0",
|
||||||
"arrive": "^2.4.1",
|
"arrive": "^2.5.3",
|
||||||
"avsc": "^5.7.7",
|
"assert": "^2.1.0",
|
||||||
|
"avsc": "^5.7.9",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"bignumber.js": "^9.1.2",
|
"bignumber.js": "^9.3.1",
|
||||||
"blakejs": "^1.2.1",
|
"blakejs": "^1.2.1",
|
||||||
"bootstrap": "4.6.2",
|
"bootstrap": "4.6.2",
|
||||||
"bootstrap-colorpicker": "^3.4.0",
|
"bootstrap-colorpicker": "^3.4.0",
|
||||||
@ -122,44 +120,45 @@
|
|||||||
"ctph.js": "0.0.5",
|
"ctph.js": "0.0.5",
|
||||||
"d3": "7.9.0",
|
"d3": "7.9.0",
|
||||||
"d3-hexbin": "^0.2.2",
|
"d3-hexbin": "^0.2.2",
|
||||||
"diff": "^5.2.0",
|
"diff": "^5.2.2",
|
||||||
"dompurify": "^3.2.5",
|
"dompurify": "^3.4.1",
|
||||||
"es6-promisify": "^7.0.0",
|
"es6-promisify": "^7.0.0",
|
||||||
"escodegen": "^2.1.0",
|
"escodegen": "^2.1.0",
|
||||||
"esprima": "^4.0.1",
|
"esprima": "^4.0.1",
|
||||||
|
"events": "^3.3.0",
|
||||||
"exif-parser": "^0.1.12",
|
"exif-parser": "^0.1.12",
|
||||||
"fernet": "^0.4.0",
|
"fernet": "^0.3.3",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"flat": "^6.0.1",
|
"flat": "^6.0.1",
|
||||||
"geodesy": "1.1.3",
|
"geodesy": "1.1.3",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.9",
|
||||||
"hash-wasm": "^4.12.0",
|
"hash-wasm": "^4.12.0",
|
||||||
"highlight.js": "^11.9.0",
|
"highlight.js": "^11.11.1",
|
||||||
"ieee754": "^1.2.1",
|
"ieee754": "^1.2.1",
|
||||||
"jimp": "^0.22.12",
|
"jimp": "1.6.0",
|
||||||
"jq-web": "^0.5.1",
|
"jq-web": "^0.5.1",
|
||||||
"jquery": "3.7.1",
|
"jquery": "3.7.1",
|
||||||
"js-sha3": "^0.9.3",
|
"js-sha3": "^0.9.3",
|
||||||
"jsesc": "^3.0.2",
|
"jsesc": "^3.1.0",
|
||||||
"json5": "^2.2.3",
|
"json5": "^2.2.3",
|
||||||
"jsonata": "^2.0.3",
|
"jsonata": "^2.1.0",
|
||||||
"jsonpath-plus": "^10.3.0",
|
"jsonpath-plus": "^10.4.0",
|
||||||
"jsonwebtoken": "8.5.1",
|
"jsonwebtoken": "9.0.3",
|
||||||
"jsqr": "^1.4.0",
|
"jsqr": "^1.4.0",
|
||||||
"jsrsasign": "^11.1.0",
|
"jsrsasign": "^11.1.3",
|
||||||
"kbpgp": "^2.1.17",
|
"kbpgp": "^2.1.17",
|
||||||
"libbzip2-wasm": "0.0.4",
|
"libbzip2-wasm": "0.0.4",
|
||||||
"libyara-wasm": "^1.2.1",
|
"libyara-wasm": "^1.2.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.18.1",
|
||||||
"loglevel": "^1.9.1",
|
"loglevel": "^1.9.2",
|
||||||
"loglevel-message-prefix": "^3.0.0",
|
"loglevel-message-prefix": "^3.0.0",
|
||||||
"lz-string": "^1.5.0",
|
"lz-string": "^1.5.0",
|
||||||
"lz4js": "^0.2.0",
|
"lz4js": "^0.2.0",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.1",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"moment-timezone": "^0.5.45",
|
"moment-timezone": "^0.6.1",
|
||||||
"ngeohash": "^0.6.3",
|
"ngeohash": "^0.6.3",
|
||||||
"node-forge": "^1.3.1",
|
"node-forge": "^1.4.0",
|
||||||
"node-md6": "^0.1.0",
|
"node-md6": "^0.1.0",
|
||||||
"nodom": "^2.4.0",
|
"nodom": "^2.4.0",
|
||||||
"notepack.io": "^3.0.1",
|
"notepack.io": "^3.0.1",
|
||||||
@ -169,24 +168,26 @@
|
|||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"popper.js": "^1.16.1",
|
"popper.js": "^1.16.1",
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"protobufjs": "^7.3.1",
|
"protobufjs": "^7.5.5",
|
||||||
"qr-image": "^3.2.0",
|
"qr-image": "^3.2.0",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rison": "^0.1.1",
|
"rison": "^0.1.1",
|
||||||
"scryptsy": "^2.1.0",
|
"scryptsy": "^2.1.0",
|
||||||
"snackbarjs": "^1.1.0",
|
"snackbarjs": "^1.1.0",
|
||||||
"sortablejs": "^1.15.2",
|
"sortablejs": "^1.15.7",
|
||||||
"split.js": "^1.6.5",
|
"split.js": "^1.6.5",
|
||||||
|
"sql-formatter": "^15.7.3",
|
||||||
"ssdeep.js": "0.0.3",
|
"ssdeep.js": "0.0.3",
|
||||||
"stream-browserify": "^3.0.0",
|
"stream-browserify": "^3.0.0",
|
||||||
"tesseract.js": "5.1.0",
|
"tesseract.js": "^6.0.1",
|
||||||
"ua-parser-js": "^1.0.38",
|
"ua-parser-js": "^1.0.41",
|
||||||
"unorm": "^1.6.0",
|
"unorm": "^1.6.0",
|
||||||
|
"url": "^0.11.4",
|
||||||
"utf8": "^3.0.0",
|
"utf8": "^3.0.0",
|
||||||
"uuid": "^11.1.0",
|
"uuid": "^13.0.0",
|
||||||
"vkbeautify": "^0.99.3",
|
"vkbeautify": "^0.99.3",
|
||||||
"xpath": "0.0.34",
|
"xpath": "0.0.34",
|
||||||
"xregexp": "^5.1.1",
|
"xregexp": "^5.1.2",
|
||||||
"zlibjs": "^0.3.1"
|
"zlibjs": "^0.3.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -200,9 +201,10 @@
|
|||||||
"testuidev": "npx nightwatch --env=dev",
|
"testuidev": "npx nightwatch --env=dev",
|
||||||
"lint": "npx grunt lint",
|
"lint": "npx grunt lint",
|
||||||
"lint:grammar": "cspell ./src",
|
"lint:grammar": "cspell ./src",
|
||||||
"postinstall": "npx grunt exec:fixCryptoApiImports && npx grunt exec:fixSnackbarMarkup && npx grunt exec:fixJimpModule",
|
"postinstall": "npx grunt exec:fixCryptoApiImports && npx grunt exec:fixSnackbarMarkup",
|
||||||
"newop": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newOperation.mjs",
|
"newop": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newOperation.mjs",
|
||||||
"minor": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newMinorVersion.mjs",
|
"minor": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newMinorVersion.mjs && npm version minor --git-tag-version=false && echo \"Updated to version v$(npm pkg get version | xargs), please create a pull request and once merged use 'npm run tag'\"",
|
||||||
|
"tag": "git tag -s \"v$(npm pkg get version | xargs)\" -m \"$(npm pkg get version | xargs)\" && echo \"Created v$(npm pkg get version | xargs), now check and push the tag\"",
|
||||||
"getheapsize": "node -e 'console.log(`node heap limit = ${require(\"v8\").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)'",
|
"getheapsize": "node -e 'console.log(`node heap limit = ${require(\"v8\").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)'",
|
||||||
"setheapsize": "export NODE_OPTIONS=--max_old_space_size=2048"
|
"setheapsize": "export NODE_OPTIONS=--max_old_space_size=2048"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,8 +55,15 @@ class Chef {
|
|||||||
progress = await recipe.execute(this.dish, progress);
|
progress = await recipe.execute(this.dish, progress);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error(err);
|
log.error(err);
|
||||||
|
|
||||||
|
let displayStr;
|
||||||
|
if ("displayStr" in err) {
|
||||||
|
displayStr = err.displayStr;
|
||||||
|
} else {
|
||||||
|
displayStr = err.toString();
|
||||||
|
}
|
||||||
error = {
|
error = {
|
||||||
displayStr: err.displayStr,
|
displayStr: displayStr,
|
||||||
};
|
};
|
||||||
progress = err.progress;
|
progress = err.progress;
|
||||||
}
|
}
|
||||||
|
|||||||
7
src/core/Ingredient.mjs
Executable file → Normal file
7
src/core/Ingredient.mjs
Executable file → Normal file
@ -5,7 +5,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Utils from "./Utils.mjs";
|
import Utils from "./Utils.mjs";
|
||||||
import {fromHex} from "./lib/Hex.mjs";
|
import { fromHex } from "./lib/Hex.mjs";
|
||||||
|
import OperationError from "./errors/OperationError.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The arguments to operations.
|
* The arguments to operations.
|
||||||
@ -119,7 +120,9 @@ class Ingredient {
|
|||||||
number = parseFloat(data);
|
number = parseFloat(data);
|
||||||
if (isNaN(number)) {
|
if (isNaN(number)) {
|
||||||
const sample = Utils.truncate(data.toString(), 10);
|
const sample = Utils.truncate(data.toString(), 10);
|
||||||
throw "Invalid ingredient value. Not a number: " + sample;
|
throw new OperationError(
|
||||||
|
"Invalid ingredient value. Not a number: " + sample,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return number;
|
return number;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Dish from "./Dish.mjs";
|
import Dish from "./Dish.mjs";
|
||||||
|
import OperationError from "./errors/OperationError.mjs";
|
||||||
import Ingredient from "./Ingredient.mjs";
|
import Ingredient from "./Ingredient.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -223,7 +224,11 @@ class Operation {
|
|||||||
*/
|
*/
|
||||||
set ingValues(ingValues) {
|
set ingValues(ingValues) {
|
||||||
ingValues.forEach((val, i) => {
|
ingValues.forEach((val, i) => {
|
||||||
this._ingList[i].value = val;
|
try {
|
||||||
|
this._ingList[i].value = val;
|
||||||
|
} catch (err) {
|
||||||
|
throw new OperationError(`Failed to set value of ingredient '${this._ingList[i].name}': ${err}`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -70,11 +70,15 @@ class Recipe {
|
|||||||
if (o instanceof Operation) {
|
if (o instanceof Operation) {
|
||||||
return o;
|
return o;
|
||||||
} else {
|
} else {
|
||||||
const op = new modules[o.module][o.name]();
|
try {
|
||||||
op.ingValues = o.ingValues;
|
const op = new modules[o.module][o.name]();
|
||||||
op.breakpoint = o.breakpoint;
|
op.ingValues = o.ingValues;
|
||||||
op.disabled = o.disabled;
|
op.breakpoint = o.breakpoint;
|
||||||
return op;
|
op.disabled = o.disabled;
|
||||||
|
return op;
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`Failed to hydrate operation '${o.name}': ${err}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -229,6 +233,7 @@ class Recipe {
|
|||||||
}
|
}
|
||||||
this.lastRunOp = op;
|
this.lastRunOp = op;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
log.error(err);
|
||||||
// Return expected errors as output
|
// Return expected errors as output
|
||||||
if (err instanceof OperationError || err?.type === "OperationError") {
|
if (err instanceof OperationError || err?.type === "OperationError") {
|
||||||
// Cannot rely on `err instanceof OperationError` here as extending
|
// Cannot rely on `err instanceof OperationError` here as extending
|
||||||
|
|||||||
@ -41,6 +41,7 @@
|
|||||||
"From Base",
|
"From Base",
|
||||||
"To BCD",
|
"To BCD",
|
||||||
"From BCD",
|
"From BCD",
|
||||||
|
"Text-Integer Conversion",
|
||||||
"To HTML Entity",
|
"To HTML Entity",
|
||||||
"From HTML Entity",
|
"From HTML Entity",
|
||||||
"URL Encode",
|
"URL Encode",
|
||||||
@ -109,6 +110,8 @@
|
|||||||
"Rabbit",
|
"Rabbit",
|
||||||
"SM4 Encrypt",
|
"SM4 Encrypt",
|
||||||
"SM4 Decrypt",
|
"SM4 Decrypt",
|
||||||
|
"RC6 Encrypt",
|
||||||
|
"RC6 Decrypt",
|
||||||
"GOST Encrypt",
|
"GOST Encrypt",
|
||||||
"GOST Decrypt",
|
"GOST Decrypt",
|
||||||
"GOST Sign",
|
"GOST Sign",
|
||||||
@ -164,7 +167,10 @@
|
|||||||
"Typex",
|
"Typex",
|
||||||
"Lorenz",
|
"Lorenz",
|
||||||
"Colossus",
|
"Colossus",
|
||||||
"SIGABA"
|
"SIGABA",
|
||||||
|
"Flask Session Decode",
|
||||||
|
"Flask Session Sign",
|
||||||
|
"Flask Session Verify"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -222,6 +228,9 @@
|
|||||||
"Subtract",
|
"Subtract",
|
||||||
"Multiply",
|
"Multiply",
|
||||||
"Divide",
|
"Divide",
|
||||||
|
"Modular Exponentiation",
|
||||||
|
"Modular Inverse",
|
||||||
|
"Extended GCD",
|
||||||
"Mean",
|
"Mean",
|
||||||
"Median",
|
"Median",
|
||||||
"Standard Deviation",
|
"Standard Deviation",
|
||||||
@ -240,6 +249,7 @@
|
|||||||
"DNS over HTTPS",
|
"DNS over HTTPS",
|
||||||
"Strip HTTP headers",
|
"Strip HTTP headers",
|
||||||
"Dechunk HTTP response",
|
"Dechunk HTTP response",
|
||||||
|
"Parse Ethernet frame",
|
||||||
"Parse User Agent",
|
"Parse User Agent",
|
||||||
"Parse IP range",
|
"Parse IP range",
|
||||||
"Parse IPv6 address",
|
"Parse IPv6 address",
|
||||||
@ -376,6 +386,7 @@
|
|||||||
"CSS selector",
|
"CSS selector",
|
||||||
"Extract EXIF",
|
"Extract EXIF",
|
||||||
"Extract ID3",
|
"Extract ID3",
|
||||||
|
"Extract Audio Metadata",
|
||||||
"Extract Files",
|
"Extract Files",
|
||||||
"RAKE",
|
"RAKE",
|
||||||
"Template"
|
"Template"
|
||||||
@ -505,7 +516,8 @@
|
|||||||
"View Bit Plane",
|
"View Bit Plane",
|
||||||
"Randomize Colour Palette",
|
"Randomize Colour Palette",
|
||||||
"Extract LSB",
|
"Extract LSB",
|
||||||
"ELF Info"
|
"ELF Info",
|
||||||
|
"Extract Audio Metadata"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -538,7 +550,8 @@
|
|||||||
"Hex Density chart",
|
"Hex Density chart",
|
||||||
"Scatter chart",
|
"Scatter chart",
|
||||||
"Series chart",
|
"Series chart",
|
||||||
"Heatmap chart"
|
"Heatmap chart",
|
||||||
|
"Extract Audio Metadata"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -550,7 +563,9 @@
|
|||||||
"Chi Square",
|
"Chi Square",
|
||||||
"P-list Viewer",
|
"P-list Viewer",
|
||||||
"Disassemble x86",
|
"Disassemble x86",
|
||||||
|
"Disassemble ARM",
|
||||||
"Pseudo-Random Number Generator",
|
"Pseudo-Random Number Generator",
|
||||||
|
"Pseudo-Random Integer Generator",
|
||||||
"Generate De Bruijn Sequence",
|
"Generate De Bruijn Sequence",
|
||||||
"Generate UUID",
|
"Generate UUID",
|
||||||
"Analyse UUID",
|
"Analyse UUID",
|
||||||
|
|||||||
54
src/core/config/scripts/fixCryptoApiImports.mjs
Normal file
54
src/core/config/scripts/fixCryptoApiImports.mjs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* This script updates crypto-api package
|
||||||
|
* It adds .mjs to local imports where its missing
|
||||||
|
*
|
||||||
|
* before:
|
||||||
|
* import foo from "./bar";
|
||||||
|
* after
|
||||||
|
* import foo from "./bar.mjs";
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint no-console: ["off"] */
|
||||||
|
|
||||||
|
import { readdirSync, readFileSync, writeFileSync } from "fs";
|
||||||
|
import { join } from "path";
|
||||||
|
|
||||||
|
// Base directory of crypto-api source
|
||||||
|
const baseDir = join(process.cwd(), "node_modules/crypto-api/src");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively walk a directory, updating import statements
|
||||||
|
* to include ".mjs" if missing
|
||||||
|
*/
|
||||||
|
function walk(dir) {
|
||||||
|
const entries = readdirSync(dir, { withFileTypes: true });
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.name === ".git") continue;
|
||||||
|
|
||||||
|
const fullPath = join(dir, entry.name);
|
||||||
|
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
walk(fullPath);
|
||||||
|
} else if (entry.isFile()) {
|
||||||
|
const content = readFileSync(fullPath, "utf8");
|
||||||
|
|
||||||
|
// Add .mjs to imports if not present
|
||||||
|
const updated = content.replace(
|
||||||
|
/from "(\.[^"]*)";/g,
|
||||||
|
(match, p1) => {
|
||||||
|
if (p1.endsWith(".mjs")) return match;
|
||||||
|
return `from "${p1}.mjs";`;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (updated !== content) {
|
||||||
|
writeFileSync(fullPath, updated, "utf8");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the walker
|
||||||
|
walk(baseDir);
|
||||||
28
src/core/config/scripts/fixSnackBarMarkup.mjs
Normal file
28
src/core/config/scripts/fixSnackBarMarkup.mjs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* This script updates snackbarjs package
|
||||||
|
* Replaces self-closing div with standard opening div
|
||||||
|
*
|
||||||
|
* before:
|
||||||
|
* <div id=snackbar-container/>
|
||||||
|
* after:
|
||||||
|
* <div id=snackbar-container>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint no-console: ["off"] */
|
||||||
|
|
||||||
|
import { readFileSync, writeFileSync } from "fs";
|
||||||
|
import { join } from "path";
|
||||||
|
|
||||||
|
// Base directory of snackbarjs source
|
||||||
|
const filePath = join(process.cwd(), "node_modules/snackbarjs/src/snackbar.js");
|
||||||
|
|
||||||
|
const content = readFileSync(filePath, "utf8");
|
||||||
|
|
||||||
|
// Replace self-closing div with standard opening div
|
||||||
|
const updated = content.replace(
|
||||||
|
/<div id=snackbar-container\/>/g,
|
||||||
|
"<div id=snackbar-container>"
|
||||||
|
);
|
||||||
|
|
||||||
|
writeFileSync(filePath, updated, "utf8");
|
||||||
@ -7,138 +7,197 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint no-console: ["off"] */
|
/* eslint no-console: ["off"] */
|
||||||
|
/* eslint jsdoc/require-jsdoc: ["off"] */
|
||||||
|
|
||||||
import prompt from "prompt";
|
|
||||||
import colors from "colors";
|
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import process from "process";
|
import process from "process";
|
||||||
|
import { execSync } from "child_process";
|
||||||
|
|
||||||
const dir = path.join(process.cwd() + "/src/core/config/");
|
const ignoredAuthors = ["github-advanced-security[bot]", "dependabot[bot]"];
|
||||||
if (!fs.existsSync(dir)) {
|
|
||||||
console.log("\nCWD: " + process.cwd());
|
|
||||||
console.log("Error: newMinorVersion.mjs should be run from the project root");
|
|
||||||
console.log("Example> node --experimental-modules src/core/config/scripts/newMinorVersion.mjs");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let changelogData = fs.readFileSync(path.join(process.cwd(), "CHANGELOG.md"), "utf8");
|
async function main() {
|
||||||
const lastVersion = changelogData.match(/## Details\s+### \[(\d+)\.(\d+)\.(\d+)\]/);
|
const dir = path.join(process.cwd() + "/src/core/config/");
|
||||||
const newVersion = [
|
if (!fs.existsSync(dir)) {
|
||||||
parseInt(lastVersion[1], 10),
|
console.log("\nCWD: " + process.cwd());
|
||||||
parseInt(lastVersion[2], 10) + 1,
|
console.log(
|
||||||
0
|
"Error: newMinorVersion.mjs should be run from the project root",
|
||||||
];
|
);
|
||||||
|
console.log(
|
||||||
|
"Example> node --experimental-modules src/core/config/scripts/newMinorVersion.mjs",
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
let knownContributors = changelogData.match(/^\[@([^\]]+)\]/gm);
|
let changelogData = fs.readFileSync(
|
||||||
knownContributors = knownContributors.map(c => c.slice(2, -1));
|
path.join(process.cwd(), "CHANGELOG.md"),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
const lastVersion = changelogData.match(
|
||||||
|
/## Details\s+### \[(\d+)\.(\d+)\.(\d+)\]/,
|
||||||
|
);
|
||||||
|
const newVersion = [
|
||||||
|
parseInt(lastVersion[1], 10),
|
||||||
|
parseInt(lastVersion[2], 10) + 1,
|
||||||
|
0,
|
||||||
|
];
|
||||||
|
|
||||||
const date = (new Date()).toISOString().split("T")[0];
|
let knownContributors = changelogData.match(/^\[@([^\]]+)\]/gm);
|
||||||
|
knownContributors = knownContributors.map((c) => c.slice(2, -1));
|
||||||
|
|
||||||
const schema = {
|
const date = new Date().toISOString().split("T")[0];
|
||||||
properties: {
|
|
||||||
message: {
|
const lastVersionSha = execSync(
|
||||||
description: "A short but descriptive summary of a feature in this version",
|
`git rev-list -n 1 v${lastVersion[1]}.${lastVersion[2]}.${lastVersion[3]}`,
|
||||||
example: "Added 'Op name' operation",
|
{
|
||||||
prompt: "Feature description",
|
encoding: "utf8",
|
||||||
type: "string",
|
|
||||||
required: true,
|
|
||||||
},
|
},
|
||||||
author: {
|
).trim();
|
||||||
description: "The author of the feature (only one supported, edit manually to add more)",
|
if (lastVersionSha.length !== 40) {
|
||||||
example: "n1474335",
|
throw new Error(
|
||||||
prompt: "Author",
|
`Unexpected output from git rev-list: ${lastVersionSha}`,
|
||||||
type: "string",
|
);
|
||||||
default: "n1474335"
|
}
|
||||||
},
|
|
||||||
id: {
|
const features = [];
|
||||||
description: "The PR number or full commit hash for this feature.",
|
|
||||||
example: "1200",
|
const commits = await (
|
||||||
prompt: "Pull request or commit ID",
|
await fetch(`https://api.github.com/repos/gchq/cyberchef/commits`)
|
||||||
type: "string"
|
).json();
|
||||||
},
|
let foundLast = false;
|
||||||
another: {
|
for (const commit of commits) {
|
||||||
description: "y/n",
|
if (commit.sha === lastVersionSha) {
|
||||||
example: "y",
|
foundLast = true;
|
||||||
prompt: "Add another feature?",
|
break;
|
||||||
type: "string",
|
} else {
|
||||||
pattern: /^[yn]$/,
|
const feature = {
|
||||||
|
message: "",
|
||||||
|
authors: [],
|
||||||
|
id: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const msgparts = commit.commit.message.split("\n\n");
|
||||||
|
feature.message = msgparts[0];
|
||||||
|
const prIdMatch = feature.message.match(/\(#(\d+)\)$/);
|
||||||
|
if (prIdMatch !== null) {
|
||||||
|
feature.message = feature.message
|
||||||
|
.replace(prIdMatch[0], "")
|
||||||
|
.trim();
|
||||||
|
feature.id = prIdMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ignoredAuthors.includes(commit.author.login)) {
|
||||||
|
feature.authors.push(commit.author.login);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msgparts.length > 1) {
|
||||||
|
msgparts[1]
|
||||||
|
.split("\n")
|
||||||
|
.filter((line) => line.startsWith("Co-authored-by: "))
|
||||||
|
.forEach((line) => {
|
||||||
|
let coAuthor = line.slice("Co-authored-by: ".length);
|
||||||
|
if (coAuthor.indexOf(">") !== -1) {
|
||||||
|
const email = coAuthor.slice(
|
||||||
|
coAuthor.indexOf("<") + 1,
|
||||||
|
coAuthor.indexOf(">"),
|
||||||
|
);
|
||||||
|
if (email.endsWith("@users.noreply.github.com")) {
|
||||||
|
coAuthor = email.slice(
|
||||||
|
email.indexOf("+") + 1,
|
||||||
|
-"@users.noreply.github.com".length,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
"Could not get ID of co-author: " +
|
||||||
|
coAuthor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
"Could not get email of co-author: " + coAuthor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!ignoredAuthors.includes(coAuthor)) {
|
||||||
|
feature.authors.push(coAuthor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
features.push(feature);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
if (!foundLast) {
|
||||||
|
throw new Error(
|
||||||
|
`Could not find last version commit: ${lastVersionSha} - need to add paging functionality`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Build schema
|
let message = `### [${newVersion[0]}.${newVersion[1]}.${newVersion[2]}] - ${date}\n`;
|
||||||
for (const prop in schema.properties) {
|
|
||||||
const p = schema.properties[prop];
|
|
||||||
p.description = "\n" + colors.white(p.description) + colors.cyan("\nExample: " + p.example) + "\n" + colors.green(p.prompt);
|
|
||||||
}
|
|
||||||
|
|
||||||
prompt.message = "";
|
const authors = [];
|
||||||
prompt.delimiter = ":".green;
|
const prIDs = [];
|
||||||
|
const commitIDs = [];
|
||||||
|
|
||||||
const features = [];
|
features.forEach((feature) => {
|
||||||
const authors = [];
|
const id =
|
||||||
const prIDs = [];
|
feature.id.length > 10 ? feature.id.slice(0, 7) : "#" + feature.id;
|
||||||
const commitIDs = [];
|
message += `- ${feature.message} ${feature.authors.map((a) => `[@${a}]`).join(" ")} | [${id}]\n`;
|
||||||
|
|
||||||
prompt.start();
|
feature.authors.forEach((author) => {
|
||||||
|
if (!knownContributors.includes(author)) {
|
||||||
|
knownContributors.push(author);
|
||||||
|
authors.push(`[@${author}]: https://github.com/${author}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const getFeature = function() {
|
if (feature.id.length > 10) {
|
||||||
prompt.get(schema, (err, result) => {
|
commitIDs.push(
|
||||||
if (err) {
|
`[${id}]: https://github.com/gchq/CyberChef/commit/${feature.id}`,
|
||||||
console.log("\nExiting script.");
|
);
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
features.push(result);
|
|
||||||
|
|
||||||
if (result.another === "y") {
|
|
||||||
getFeature();
|
|
||||||
} else {
|
} else {
|
||||||
let message = `### [${newVersion[0]}.${newVersion[1]}.${newVersion[2]}] - ${date}\n`;
|
prIDs.push(
|
||||||
|
`[#${feature.id}]: https://github.com/gchq/CyberChef/pull/${feature.id}`,
|
||||||
features.forEach(feature => {
|
);
|
||||||
const id = feature.id.length > 10 ? feature.id.slice(0, 7) : "#" + feature.id;
|
|
||||||
message += `- ${feature.message} [@${feature.author}] | [${id}]\n`;
|
|
||||||
|
|
||||||
if (!knownContributors.includes(feature.author)) {
|
|
||||||
authors.push(`[@${feature.author}]: https://github.com/${feature.author}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (feature.id.length > 10) {
|
|
||||||
commitIDs.push(`[${id}]: https://github.com/gchq/CyberChef/commit/${feature.id}`);
|
|
||||||
} else {
|
|
||||||
prIDs.push(`[#${feature.id}]: https://github.com/gchq/CyberChef/pull/${feature.id}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Message
|
|
||||||
changelogData = changelogData.replace(/## Details\n\n/, "## Details\n\n" + message + "\n");
|
|
||||||
|
|
||||||
// Tag
|
|
||||||
const newTag = `[${newVersion[0]}.${newVersion[1]}.${newVersion[2]}]: https://github.com/gchq/CyberChef/releases/tag/v${newVersion[0]}.${newVersion[1]}.${newVersion[2]}\n`;
|
|
||||||
changelogData = changelogData.replace(/\n\n(\[\d+\.\d+\.\d+\]: https)/, "\n\n" + newTag + "$1");
|
|
||||||
|
|
||||||
// Author
|
|
||||||
authors.forEach(author => {
|
|
||||||
changelogData = changelogData.replace(/(\n\[@[^\]]+\]: https:\/\/github\.com\/[^\n]+\n)\n/, "$1" + author + "\n\n");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Commit IDs
|
|
||||||
commitIDs.forEach(commitID => {
|
|
||||||
changelogData = changelogData.replace(/(\n\[[^\].]+\]: https:\/\/github.com\/gchq\/CyberChef\/commit\/[^\n]+\n)\n/, "$1" + commitID + "\n\n");
|
|
||||||
});
|
|
||||||
|
|
||||||
// PR IDs
|
|
||||||
prIDs.forEach(prID => {
|
|
||||||
changelogData = changelogData.replace(/(\n\[#[^\]]+\]: https:\/\/github.com\/gchq\/CyberChef\/pull\/[^\n]+\n)\n*$/, "$1" + prID + "\n\n");
|
|
||||||
});
|
|
||||||
|
|
||||||
fs.writeFileSync(path.join(process.cwd(), "CHANGELOG.md"), changelogData);
|
|
||||||
|
|
||||||
console.log("Written CHANGELOG.md\nCommit changes and then run `npm version minor`.");
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
getFeature();
|
// Message
|
||||||
|
changelogData = changelogData.replace(
|
||||||
|
/## Details\n\n/,
|
||||||
|
"## Details\n\n" + message + "\n",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Tag
|
||||||
|
const newTag = `[${newVersion[0]}.${newVersion[1]}.${newVersion[2]}]: https://github.com/gchq/CyberChef/releases/tag/v${newVersion[0]}.${newVersion[1]}.${newVersion[2]}\n`;
|
||||||
|
changelogData = changelogData.replace(
|
||||||
|
/\n\n(\[\d+\.\d+\.\d+\]: https)/,
|
||||||
|
"\n\n" + newTag + "$1",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Author
|
||||||
|
authors.forEach((author) => {
|
||||||
|
changelogData = changelogData.replace(
|
||||||
|
/(\n\[@[^\]]+\]: https:\/\/github\.com\/[^\n]+\n)\n/,
|
||||||
|
"$1" + author + "\n\n",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Commit IDs
|
||||||
|
commitIDs.forEach((commitID) => {
|
||||||
|
changelogData = changelogData.replace(
|
||||||
|
/(\n\[[^\].]+\]: https:\/\/github.com\/gchq\/CyberChef\/commit\/[^\n]+\n)\n/,
|
||||||
|
"$1" + commitID + "\n\n",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// PR IDs
|
||||||
|
prIDs.forEach((prID) => {
|
||||||
|
changelogData = changelogData.replace(
|
||||||
|
/(\n\[#[^\]]+\]: https:\/\/github.com\/gchq\/CyberChef\/(?:pull|issues)\/[^\n]+\n)\n*$/,
|
||||||
|
"$1" + prID + "\n\n",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.writeFileSync(path.join(process.cwd(), "CHANGELOG.md"), changelogData);
|
||||||
|
}
|
||||||
|
main().catch(console.error);
|
||||||
|
|||||||
103
src/core/lib/AudioBytes.mjs
Normal file
103
src/core/lib/AudioBytes.mjs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* Byte-reading and text-decoding utilities for audio metadata parsing.
|
||||||
|
*
|
||||||
|
* @author d0s1nt [d0s1nt@cyberchefaudio]
|
||||||
|
* @copyright Crown Copyright 2025
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @returns {string} 4-byte ASCII at offset, or "" if out of bounds. */
|
||||||
|
export function ascii4(b, off) {
|
||||||
|
if (off + 4 > b.length) return "";
|
||||||
|
return String.fromCharCode(b[off], b[off + 1], b[off + 2], b[off + 3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {number} Byte offset of ASCII needle `s`, or -1. */
|
||||||
|
export function indexOfAscii(b, s, start, end) {
|
||||||
|
const limit = Math.max(0, Math.min(end, b.length) - s.length);
|
||||||
|
for (let i = start; i <= limit; i++) {
|
||||||
|
let ok = true;
|
||||||
|
for (let j = 0; j < s.length; j++) {
|
||||||
|
if (b[i + j] !== s.charCodeAt(j)) {
|
||||||
|
ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ok) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {number} Unsigned 32-bit big-endian read. */
|
||||||
|
export function u32be(bytes, off) {
|
||||||
|
return ((bytes[off] << 24) >>> 0) | (bytes[off + 1] << 16) | (bytes[off + 2] << 8) | bytes[off + 3];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {number} Unsigned 32-bit little-endian read. */
|
||||||
|
export function u32le(bytes, off) {
|
||||||
|
return (bytes[off] | (bytes[off + 1] << 8) | (bytes[off + 2] << 16) | (bytes[off + 3] << 24)) >>> 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {number} Unsigned 16-bit little-endian read. */
|
||||||
|
export function u16le(bytes, off) {
|
||||||
|
return bytes[off] | (bytes[off + 1] << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {BigInt} Unsigned 64-bit little-endian read. */
|
||||||
|
export function u64le(bytes, off) {
|
||||||
|
return BigInt(u32le(bytes, off)) | (BigInt(u32le(bytes, off + 4)) << 32n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {number} Decoded ID3v2 synchsafe integer from four 7-bit bytes. */
|
||||||
|
export function synchsafeToInt(b0, b1, b2, b3) {
|
||||||
|
return ((b0 & 0x7f) << 21) | ((b1 & 0x7f) << 14) | ((b2 & 0x7f) << 7) | (b3 & 0x7f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {string} Decoded UTF-16LE byte range, nulls stripped. */
|
||||||
|
export function decodeUtf16LE(b, off, len) {
|
||||||
|
if (len <= 0 || off + len > b.length) return "";
|
||||||
|
try {
|
||||||
|
return new TextDecoder("utf-16le").decode(b.slice(off, off + len)).replace(/\u0000/g, "").trim();
|
||||||
|
} catch {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {{valueBytes: Uint8Array, next: number}} Bytes until null terminator, UTF-16 aware. */
|
||||||
|
export function readNullTerminated(bytes, start, encoding) {
|
||||||
|
const isUtf16 = encoding === 1 || encoding === 2;
|
||||||
|
if (!isUtf16) {
|
||||||
|
let i = start;
|
||||||
|
while (i < bytes.length && bytes[i] !== 0x00) i++;
|
||||||
|
return { valueBytes: bytes.slice(start, i), next: i + 1 };
|
||||||
|
}
|
||||||
|
let i = start;
|
||||||
|
while (i + 1 < bytes.length && !(bytes[i] === 0x00 && bytes[i + 1] === 0x00)) i += 2;
|
||||||
|
return { valueBytes: bytes.slice(start, i), next: i + 2 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const ID3_ENCODINGS = ["iso-8859-1", "utf-16", "utf-16be", "utf-8"];
|
||||||
|
|
||||||
|
/** @returns {string} Text decoded using ID3v2 encoding byte (0=latin1, 1=utf16, 2=utf16be, 3=utf8). */
|
||||||
|
export function decodeText(bytes, encoding) {
|
||||||
|
if (!bytes || bytes.length === 0) return "";
|
||||||
|
try {
|
||||||
|
return new TextDecoder(ID3_ENCODINGS[encoding] || "utf-16").decode(bytes);
|
||||||
|
} catch {
|
||||||
|
return safeUtf8(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {string} UTF-8 decode with replacement (never throws). */
|
||||||
|
export function safeUtf8(bytes) {
|
||||||
|
try {
|
||||||
|
return new TextDecoder("utf-8", { fatal: false }).decode(bytes);
|
||||||
|
} catch {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {string} ISO-8859-1 decode, nulls stripped, trimmed. */
|
||||||
|
export function decodeLatin1Trim(bytes) {
|
||||||
|
return decodeText(bytes, 0).replace(/\u0000/g, "").trim();
|
||||||
|
}
|
||||||
82
src/core/lib/AudioMetaSchema.mjs
Normal file
82
src/core/lib/AudioMetaSchema.mjs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/**
|
||||||
|
* Report skeleton and container detection for audio metadata extraction.
|
||||||
|
*
|
||||||
|
* @author d0s1nt [d0s1nt@cyberchefaudio]
|
||||||
|
* @copyright Crown Copyright 2025
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable camelcase */
|
||||||
|
|
||||||
|
import { ascii4, indexOfAscii } from "./AudioBytes.mjs";
|
||||||
|
|
||||||
|
/** Builds the empty report skeleton ready for a format parser to populate. */
|
||||||
|
export function makeEmptyReport(filename, byteLength, container) {
|
||||||
|
return {
|
||||||
|
schema_version: "audio-meta-1.0",
|
||||||
|
artifact: {
|
||||||
|
filename,
|
||||||
|
byte_length: byteLength,
|
||||||
|
container: { type: container.type, brand: container.brand || null, mime: container.mime || null },
|
||||||
|
},
|
||||||
|
detections: { metadata_systems: [], provenance_systems: [] },
|
||||||
|
tags: {
|
||||||
|
common: {
|
||||||
|
title: null, artist: null, album: null, date: null, track: null,
|
||||||
|
genre: null, comment: null, composer: null, copyright: null, language: null,
|
||||||
|
},
|
||||||
|
raw: {},
|
||||||
|
},
|
||||||
|
embedded: [],
|
||||||
|
provenance: {
|
||||||
|
c2pa: {
|
||||||
|
present: false,
|
||||||
|
embedding: [],
|
||||||
|
manifest_store: { active_manifest_urn: null, instance_id: null, claim_generator: null },
|
||||||
|
assertions: [],
|
||||||
|
signature: {
|
||||||
|
algorithm: null, signing_time: null,
|
||||||
|
certificate: { subject_cn: null, issuer_cn: null, serial_number: null },
|
||||||
|
},
|
||||||
|
validation: { validation_state: "Unknown", reasons: [], details_raw: null },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Detects the audio container format from magic bytes. */
|
||||||
|
export function sniffContainer(b) {
|
||||||
|
if (b.length >= 3 && b[0] === 0x49 && b[1] === 0x44 && b[2] === 0x33)
|
||||||
|
return { type: "mp3", mime: "audio/mpeg" };
|
||||||
|
if (b.length >= 2 && b[0] === 0xff && (b[1] & 0xe0) === 0xe0) {
|
||||||
|
if ((b[1] & 0x06) === 0x00) return { type: "aac", mime: "audio/aac" };
|
||||||
|
return { type: "mp3", mime: "audio/mpeg" };
|
||||||
|
}
|
||||||
|
if (b.length >= 8 && b[0] === 0x0b && b[1] === 0x77)
|
||||||
|
return { type: "ac3", mime: "audio/ac3" };
|
||||||
|
if (b.length >= 16 &&
|
||||||
|
b[0] === 0x30 && b[1] === 0x26 && b[2] === 0xb2 && b[3] === 0x75 &&
|
||||||
|
b[4] === 0x8e && b[5] === 0x66 && b[6] === 0xcf && b[7] === 0x11)
|
||||||
|
return { type: "wma", mime: "audio/x-ms-wma" };
|
||||||
|
if (b.length >= 12 && ascii4(b, 0) === "RIFF" && ascii4(b, 8) === "WAVE")
|
||||||
|
return { type: "wav", mime: "audio/wav" };
|
||||||
|
if (b.length >= 12 && ascii4(b, 0) === "BW64" && ascii4(b, 8) === "WAVE")
|
||||||
|
return { type: "bw64", mime: "audio/wav" };
|
||||||
|
if (b.length >= 4 && ascii4(b, 0) === "fLaC")
|
||||||
|
return { type: "flac", mime: "audio/flac" };
|
||||||
|
if (b.length >= 4 && ascii4(b, 0) === "OggS") {
|
||||||
|
const idx = indexOfAscii(b, "OpusHead", 0, Math.min(b.length, 65536));
|
||||||
|
return idx >= 0 ? { type: "opus", mime: "audio/ogg" } : { type: "ogg", mime: "audio/ogg" };
|
||||||
|
}
|
||||||
|
if (b.length >= 12 && ascii4(b, 4) === "ftyp") {
|
||||||
|
const brand = ascii4(b, 8);
|
||||||
|
const isM4A = brand === "M4A " || brand === "M4B " || brand === "M4P ";
|
||||||
|
return { type: isM4A ? "m4a" : "mp4", mime: isM4A ? "audio/mp4" : "video/mp4", brand };
|
||||||
|
}
|
||||||
|
if (b.length >= 12 && ascii4(b, 0) === "FORM") {
|
||||||
|
const formType = ascii4(b, 8);
|
||||||
|
if (formType === "AIFF" || formType === "AIFC") return { type: "aiff", mime: "audio/aiff", brand: formType };
|
||||||
|
}
|
||||||
|
return { type: "unknown", mime: null };
|
||||||
|
}
|
||||||
630
src/core/lib/AudioParsers.mjs
Normal file
630
src/core/lib/AudioParsers.mjs
Normal file
@ -0,0 +1,630 @@
|
|||||||
|
/**
|
||||||
|
* Format-specific audio metadata parsers.
|
||||||
|
*
|
||||||
|
* @author d0s1nt [d0s1nt@cyberchefaudio]
|
||||||
|
* @copyright Crown Copyright 2025
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable camelcase */
|
||||||
|
|
||||||
|
import {
|
||||||
|
ascii4, indexOfAscii,
|
||||||
|
u32be, u32le, u16le, u64le, synchsafeToInt,
|
||||||
|
decodeUtf16LE, readNullTerminated, decodeText,
|
||||||
|
safeUtf8, decodeLatin1Trim,
|
||||||
|
} from "./AudioBytes.mjs";
|
||||||
|
|
||||||
|
/** Parses MP3 metadata: ID3v2 frames, ID3v1 footer, APEv2 tags. */
|
||||||
|
export function parseMp3(b, report) {
|
||||||
|
processId3v2(b, report);
|
||||||
|
processId3v1(b, report);
|
||||||
|
|
||||||
|
const ape = parseApeV2BestEffort(b);
|
||||||
|
if (ape) {
|
||||||
|
report.detections.metadata_systems.push("apev2");
|
||||||
|
report.tags.raw.apev2 = ape;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Iterates ID3v2 frames and populates the report. */
|
||||||
|
function processId3v2(b, report) {
|
||||||
|
report.detections.metadata_systems.push("id3v2");
|
||||||
|
|
||||||
|
const id3 = parseId3v2(b);
|
||||||
|
report.tags.raw.id3v2 = id3 ? { header: id3.header, frames: [] } : null;
|
||||||
|
|
||||||
|
if (id3) {
|
||||||
|
for (const f of id3.frames) {
|
||||||
|
const entry = { id: f.id, size: f.size, description: ID3_FRAME_DESCRIPTIONS[f.id] || null };
|
||||||
|
|
||||||
|
if (f.id[0] === "T" && f.id !== "TXXX") {
|
||||||
|
const text = f.data?.length >= 1 ?
|
||||||
|
decodeText(f.data.slice(1), f.data[0]).replace(/\u0000/g, "").trim() :
|
||||||
|
"";
|
||||||
|
entry.decoded = text;
|
||||||
|
if (f.id === "TLEN") {
|
||||||
|
const ms = normalizeTlen(text);
|
||||||
|
if (ms !== null) entry.normalized_ms = ms;
|
||||||
|
}
|
||||||
|
mapCommonId3(report, f.id, text);
|
||||||
|
} else if (f.id === "TXXX") {
|
||||||
|
const txxx = decodeTxxx(f.data);
|
||||||
|
entry.decoded = txxx;
|
||||||
|
if (!report.tags.raw.id3v2.txxx) report.tags.raw.id3v2.txxx = [];
|
||||||
|
report.tags.raw.id3v2.txxx.push(txxx);
|
||||||
|
} else if (f.id === "COMM") {
|
||||||
|
const comm = decodeCommFrame(f.data);
|
||||||
|
entry.decoded = comm;
|
||||||
|
if (comm?.text && !report.tags.common.comment) report.tags.common.comment = comm.text;
|
||||||
|
} else if (f.id === "GEOB") {
|
||||||
|
processGeobFrame(f, entry, report);
|
||||||
|
}
|
||||||
|
|
||||||
|
report.tags.raw.id3v2.frames.push(entry);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
report.detections.metadata_systems = report.detections.metadata_systems.filter((x) => x !== "id3v2");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parses GEOB frame contents, populates entry, embedded objects, and C2PA provenance. */
|
||||||
|
function processGeobFrame(f, entry, report) {
|
||||||
|
const d = f.data, enc = d[0];
|
||||||
|
let off = 1;
|
||||||
|
const mime = readNullTerminated(d, off, 0);
|
||||||
|
const mimeType = decodeLatin1Trim(mime.valueBytes);
|
||||||
|
off = mime.next;
|
||||||
|
const file = readNullTerminated(d, off, enc);
|
||||||
|
const filename = decodeText(file.valueBytes, enc).replace(/\u0000/g, "").trim();
|
||||||
|
off = file.next;
|
||||||
|
const desc = readNullTerminated(d, off, enc);
|
||||||
|
const description = decodeText(desc.valueBytes, enc).replace(/\u0000/g, "").trim();
|
||||||
|
off = desc.next;
|
||||||
|
const objLen = d.length - off;
|
||||||
|
|
||||||
|
entry.geob = { mimeType, filename, description, object_bytes: objLen };
|
||||||
|
const geobId = `geob_${report.embedded.filter((x) => x.source === "id3v2:GEOB").length}`;
|
||||||
|
report.embedded.push({
|
||||||
|
id: geobId, source: "id3v2:GEOB",
|
||||||
|
content_type: mimeType || null, byte_length: objLen,
|
||||||
|
description: description || null, filename: filename || null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mt = (mimeType || "").toLowerCase();
|
||||||
|
if (mt.includes("c2pa") || mt.includes("jumbf") || mt.includes("application/x-c2pa-manifest-store")) {
|
||||||
|
report.provenance.c2pa.present = true;
|
||||||
|
report.provenance.c2pa.embedding.push({
|
||||||
|
carrier: "id3v2:GEOB", content_type: mimeType || null, byte_length: objLen,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Processes the 128-byte ID3v1 footer tag. */
|
||||||
|
function processId3v1(b, report) {
|
||||||
|
const id3v1 = parseId3v1(b);
|
||||||
|
if (!id3v1) return;
|
||||||
|
|
||||||
|
report.detections.metadata_systems.push("id3v1");
|
||||||
|
report.tags.raw.id3v1 = id3v1;
|
||||||
|
mapCommon(report, id3v1, ID3V1_TO_COMMON);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parses WAV/BWF/BW64 RIFF chunks: LIST/INFO, bext, iXML, axml, ds64. */
|
||||||
|
export function parseRiffWave(b, report, maxTextBytes) {
|
||||||
|
report.detections.metadata_systems.push("riff_info");
|
||||||
|
|
||||||
|
const chunks = enumerateChunks(b, 12, b.length, 50000);
|
||||||
|
const riff = { chunks: [], info: null, bext: null, ixml: null, axml: null, ds64: null };
|
||||||
|
|
||||||
|
const info = {};
|
||||||
|
for (const c of chunks) {
|
||||||
|
riff.chunks.push({ id: c.id, size: c.size, offset: c.dataOff });
|
||||||
|
processRiffChunk(b, c, riff, info, report, maxTextBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
riff.info = Object.keys(info).length ? info : null;
|
||||||
|
report.tags.raw.riff = riff;
|
||||||
|
if (riff.info) mapCommon(report, riff.info, RIFF_TO_COMMON);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Processes a single RIFF chunk, updating riff state and the report. */
|
||||||
|
function processRiffChunk(b, c, riff, info, report, maxTextBytes) {
|
||||||
|
if (c.id === "ds64") {
|
||||||
|
riff.ds64 = { present: true, size: c.size };
|
||||||
|
if (!report.detections.metadata_systems.includes("bw64_ds64")) report.detections.metadata_systems.push("bw64_ds64");
|
||||||
|
if (report.artifact.container.type === "wav") report.artifact.container.type = "bw64";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.id === "LIST" && ascii4(b, c.dataOff) === "INFO") {
|
||||||
|
for (const s of enumerateChunks(b, c.dataOff + 4, c.dataOff + c.size, 10000))
|
||||||
|
info[s.id] = decodeLatin1Trim(b.slice(s.dataOff, s.dataOff + s.size));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.id === "bext") {
|
||||||
|
if (!report.detections.metadata_systems.includes("bwf_bext")) report.detections.metadata_systems.push("bwf_bext");
|
||||||
|
riff.bext = parseBext(b, c.dataOff, c.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.id === "iXML" || c.id === "axml") {
|
||||||
|
const key = c.id === "iXML" ? "ixml" : "axml";
|
||||||
|
if (!report.detections.metadata_systems.includes(key)) report.detections.metadata_systems.push(key);
|
||||||
|
const payload = b.slice(c.dataOff, c.dataOff + c.size);
|
||||||
|
riff[key] = { xml: safeUtf8(payload.slice(0, Math.min(payload.length, maxTextBytes))), truncated: payload.length > maxTextBytes };
|
||||||
|
report.embedded.push({
|
||||||
|
id: `${key}_0`, source: `riff:${c.id}`, content_type: "application/xml",
|
||||||
|
byte_length: payload.length, description: `${c.id} chunk`, filename: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parses FLAC metablocks: STREAMINFO, Vorbis Comment, PICTURE. */
|
||||||
|
export function parseFlac(b, report, maxTextBytes) {
|
||||||
|
report.detections.metadata_systems.push("flac_metablocks");
|
||||||
|
|
||||||
|
const blocks = parseFlacMetaBlocks(b);
|
||||||
|
report.tags.raw.flac = { blocks: [] };
|
||||||
|
|
||||||
|
for (const blk of blocks) {
|
||||||
|
report.tags.raw.flac.blocks.push({ type: blk.typeName, length: blk.length });
|
||||||
|
|
||||||
|
if (blk.typeName === "VORBIS_COMMENT") {
|
||||||
|
if (!report.detections.metadata_systems.includes("vorbis_comments")) report.detections.metadata_systems.push("vorbis_comments");
|
||||||
|
const vc = parseVorbisComment(blk.data);
|
||||||
|
report.tags.raw.vorbis_comments = vc;
|
||||||
|
mapVorbisCommon(report, vc);
|
||||||
|
} else if (blk.typeName === "PICTURE") {
|
||||||
|
const pic = parseFlacPicture(blk.data, maxTextBytes);
|
||||||
|
report.embedded.push({
|
||||||
|
id: `cover_art_${report.embedded.filter((x) => x.id.startsWith("cover_art_")).length}`,
|
||||||
|
source: "flac:PICTURE", content_type: pic.mime || null,
|
||||||
|
byte_length: pic.dataLength, description: pic.description || null, filename: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parses OGG/Opus Vorbis comments. */
|
||||||
|
export function parseOgg(b, report) {
|
||||||
|
if (!report.detections.metadata_systems.includes("ogg_opus_tags")) report.detections.metadata_systems.push("ogg_opus_tags");
|
||||||
|
|
||||||
|
const scanEnd = Math.min(b.length, 1024 * 1024);
|
||||||
|
let tags = null;
|
||||||
|
const opusTagsIdx = indexOfAscii(b, "OpusTags", 0, scanEnd);
|
||||||
|
if (opusTagsIdx >= 0) {
|
||||||
|
report.artifact.container.type = "opus";
|
||||||
|
tags = parseVorbisComment(b.slice(opusTagsIdx + 8, scanEnd));
|
||||||
|
} else {
|
||||||
|
const vorbisIdx = indexOfAscii(b, "\x03vorbis", 0, scanEnd);
|
||||||
|
if (vorbisIdx >= 0) tags = parseVorbisComment(b.slice(vorbisIdx + 7, scanEnd));
|
||||||
|
}
|
||||||
|
|
||||||
|
report.tags.raw.ogg = { has_opustags: opusTagsIdx >= 0, has_vorbis_comment: !!tags };
|
||||||
|
|
||||||
|
if (tags) {
|
||||||
|
if (!report.detections.metadata_systems.includes("vorbis_comments")) report.detections.metadata_systems.push("vorbis_comments");
|
||||||
|
report.tags.raw.vorbis_comments = tags;
|
||||||
|
mapVorbisCommon(report, tags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Best-effort top-level atom scan for MP4/M4A. */
|
||||||
|
export function parseMp4BestEffort(b, report) {
|
||||||
|
report.detections.metadata_systems.push("mp4_atoms");
|
||||||
|
const atoms = [];
|
||||||
|
|
||||||
|
let off = 0;
|
||||||
|
while (off + 8 <= b.length && atoms.length < 2000) {
|
||||||
|
const size = u32be(b, off);
|
||||||
|
const type = ascii4(b, off + 4);
|
||||||
|
if (size < 8) break;
|
||||||
|
atoms.push({ type, size, offset: off });
|
||||||
|
off += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
report.tags.raw.mp4 = {
|
||||||
|
top_level_atoms: atoms.slice(0, 200),
|
||||||
|
hints: {
|
||||||
|
hasMoov: atoms.some((a) => a.type === "moov"),
|
||||||
|
hasUdta: atoms.some((a) => a.type === "udta"),
|
||||||
|
hasMeta: atoms.some((a) => a.type === "meta"),
|
||||||
|
hasIlst: atoms.some((a) => a.type === "ilst"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Best-effort AIFF/AIFC chunk scanning for NAME, AUTH, ANNO. */
|
||||||
|
export function parseAiffBestEffort(b, report, maxTextBytes) {
|
||||||
|
report.detections.metadata_systems.push("aiff_chunks");
|
||||||
|
let off = 12;
|
||||||
|
const chunks = [];
|
||||||
|
while (off + 8 <= b.length && chunks.length < 2000) {
|
||||||
|
const id = ascii4(b, off);
|
||||||
|
const size = u32be(b, off + 4);
|
||||||
|
const dataOff = off + 8;
|
||||||
|
chunks.push({ id, size, offset: off });
|
||||||
|
|
||||||
|
if (["NAME", "AUTH", "ANNO", "(c) "].includes(id)) {
|
||||||
|
const txt = safeUtf8(b.slice(dataOff, dataOff + Math.min(size, maxTextBytes)));
|
||||||
|
if (!report.tags.raw.aiff) report.tags.raw.aiff = { chunks: [] };
|
||||||
|
report.tags.raw.aiff.chunks.push({ id, value: txt, truncated: size > maxTextBytes });
|
||||||
|
}
|
||||||
|
|
||||||
|
off = dataOff + size + (size % 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!report.tags.raw.aiff) report.tags.raw.aiff = {};
|
||||||
|
report.tags.raw.aiff.chunk_index = chunks.slice(0, 500);
|
||||||
|
|
||||||
|
const nameChunk = report.tags.raw.aiff?.chunks?.find((ch) => ch.id === "NAME")?.value;
|
||||||
|
if (nameChunk) report.tags.common.title = report.tags.common.title || nameChunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AAC_SAMPLE_RATES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
|
||||||
|
const AAC_PROFILES = ["Main", "LC", "SSR", "LTP"];
|
||||||
|
const AAC_CHANNELS = ["defined in AOT", "mono", "stereo", "3.0", "4.0", "5.0", "5.1", "7.1"];
|
||||||
|
|
||||||
|
/** Parses AAC ADTS frame header for audio parameters. */
|
||||||
|
export function parseAacAdts(b, report) {
|
||||||
|
report.detections.metadata_systems.push("adts_header");
|
||||||
|
if (b.length < 7) return;
|
||||||
|
|
||||||
|
const id = (b[1] >> 3) & 0x01;
|
||||||
|
const profile = (b[2] >> 6) & 0x03;
|
||||||
|
const freqIdx = (b[2] >> 2) & 0x0f;
|
||||||
|
const chanCfg = ((b[2] & 0x01) << 2) | ((b[3] >> 6) & 0x03);
|
||||||
|
|
||||||
|
report.tags.raw.aac = {
|
||||||
|
mpeg_version: id === 1 ? "MPEG-2" : "MPEG-4",
|
||||||
|
profile: AAC_PROFILES[profile] || `Profile ${profile}`,
|
||||||
|
sample_rate: AAC_SAMPLE_RATES[freqIdx] || null,
|
||||||
|
sample_rate_index: freqIdx,
|
||||||
|
channel_configuration: chanCfg,
|
||||||
|
channel_description: AAC_CHANNELS[chanCfg] || null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const AC3_SAMPLE_RATES = [48000, 44100, 32000];
|
||||||
|
const AC3_BITRATES = [32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 576, 640];
|
||||||
|
const AC3_ACMODES = [
|
||||||
|
"2.0 (Ch1+Ch2)", "1.0 (C)", "2.0 (L R)", "3.0 (L C R)",
|
||||||
|
"2.1 (L R S)", "3.1 (L C R S)", "2.2 (L R SL SR)", "3.2 (L C R SL SR)",
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Parses AC3 (Dolby Digital) bitstream info. */
|
||||||
|
export function parseAc3(b, report) {
|
||||||
|
report.detections.metadata_systems.push("ac3_bsi");
|
||||||
|
if (b.length < 8) return;
|
||||||
|
|
||||||
|
const fscod = (b[4] >> 6) & 0x03;
|
||||||
|
const frmsizecod = b[4] & 0x3f;
|
||||||
|
const bsid = (b[5] >> 3) & 0x1f;
|
||||||
|
const bsmod = b[5] & 0x07;
|
||||||
|
const acmod = (b[6] >> 5) & 0x07;
|
||||||
|
|
||||||
|
report.tags.raw.ac3 = {
|
||||||
|
sample_rate: AC3_SAMPLE_RATES[fscod] || null,
|
||||||
|
fscod,
|
||||||
|
bitrate_kbps: AC3_BITRATES[frmsizecod >> 1] || null,
|
||||||
|
frmsizecod, bsid, bsmod, acmod,
|
||||||
|
channel_layout: AC3_ACMODES[acmod] || null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parses WMA files (ASF container) for content description metadata. */
|
||||||
|
export function parseWmaAsf(b, report) {
|
||||||
|
report.detections.metadata_systems.push("asf_header");
|
||||||
|
if (b.length < 30) return;
|
||||||
|
|
||||||
|
const headerSize = Number(u64le(b, 16));
|
||||||
|
const numObjects = u32le(b, 24);
|
||||||
|
const headerEnd = Math.min(b.length, headerSize);
|
||||||
|
|
||||||
|
const objects = [];
|
||||||
|
let off = 30;
|
||||||
|
|
||||||
|
for (let i = 0; i < numObjects && off + 24 <= headerEnd; i++) {
|
||||||
|
const guid4 = [b[off], b[off + 1], b[off + 2], b[off + 3]];
|
||||||
|
const objSize = Number(u64le(b, off + 16));
|
||||||
|
if (objSize < 24 || off + objSize > headerEnd) break;
|
||||||
|
|
||||||
|
const dataOff = off + 24;
|
||||||
|
const dataLen = objSize - 24;
|
||||||
|
|
||||||
|
if (guid4[0] === 0x33 && guid4[1] === 0x26 && guid4[2] === 0xb2 && guid4[3] === 0x75 && dataLen >= 10) {
|
||||||
|
const cd = parseAsfContentDescription(b, dataOff);
|
||||||
|
if (!report.detections.metadata_systems.includes("asf_content_desc"))
|
||||||
|
report.detections.metadata_systems.push("asf_content_desc");
|
||||||
|
if (!report.tags.raw.asf) report.tags.raw.asf = {};
|
||||||
|
report.tags.raw.asf.content_description = cd;
|
||||||
|
mapCommon(report, cd, ASF_CD_TO_COMMON);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (guid4[0] === 0x40 && guid4[1] === 0xa4 && guid4[2] === 0xd0 && guid4[3] === 0xd2 && dataLen >= 2) {
|
||||||
|
const ext = parseAsfExtContentDescription(b, dataOff, dataOff + dataLen);
|
||||||
|
if (!report.detections.metadata_systems.includes("asf_ext_content_desc"))
|
||||||
|
report.detections.metadata_systems.push("asf_ext_content_desc");
|
||||||
|
if (!report.tags.raw.asf) report.tags.raw.asf = {};
|
||||||
|
report.tags.raw.asf.extended_content = ext;
|
||||||
|
|
||||||
|
const c = report.tags.common;
|
||||||
|
for (const d of ext) {
|
||||||
|
const field = WMA_TO_COMMON[(d.name || "").toUpperCase()];
|
||||||
|
if (field && d.value) c[field] = c[field] || d.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
objects.push({ guid_prefix: guid4.map(x => x.toString(16).padStart(2, "0")).join(""), size: objSize });
|
||||||
|
off += objSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!report.tags.raw.asf) report.tags.raw.asf = {};
|
||||||
|
report.tags.raw.asf.header_objects = objects;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ID3_FRAME_DESCRIPTIONS = {
|
||||||
|
TIT2: "Title/songname/content description", TPE1: "Lead performer(s)/Soloist(s)",
|
||||||
|
TRCK: "Track number/Position in set", TALB: "Album/Movie/Show title",
|
||||||
|
TDRC: "Recording time", TYER: "Year", TCON: "Content type",
|
||||||
|
TPE2: "Band/orchestra/accompaniment", TLEN: "Length (ms)", TCOM: "Composer",
|
||||||
|
COMM: "Comments", APIC: "Attached picture", GEOB: "General encapsulated object",
|
||||||
|
TXXX: "User defined text information frame", UFID: "Unique file identifier", PRIV: "Private frame",
|
||||||
|
};
|
||||||
|
|
||||||
|
const ID3_TO_COMMON = {
|
||||||
|
TIT2: "title", TPE1: "artist", TALB: "album", TDRC: "date", TYER: "date",
|
||||||
|
TRCK: "track", TCON: "genre", COMM: "comment", TCOM: "composer", TCOP: "copyright", TLAN: "language",
|
||||||
|
};
|
||||||
|
const VORBIS_TO_COMMON = {
|
||||||
|
TITLE: "title", ARTIST: "artist", ALBUM: "album", DATE: "date",
|
||||||
|
TRACKNUMBER: "track", GENRE: "genre", COMMENT: "comment", COMPOSER: "composer", LANGUAGE: "language",
|
||||||
|
};
|
||||||
|
const WMA_TO_COMMON = {
|
||||||
|
"WM/ALBUMTITLE": "album", "WM/GENRE": "genre", "WM/YEAR": "date",
|
||||||
|
"WM/TRACKNUMBER": "track", "WM/COMPOSER": "composer", "WM/LANGUAGE": "language",
|
||||||
|
};
|
||||||
|
const ID3V1_TO_COMMON = { title: "title", artist: "artist", album: "album", year: "date", comment: "comment", genre: "genre", track: "track" };
|
||||||
|
const RIFF_TO_COMMON = { INAM: "title", IART: "artist", ICMT: "comment", IGNR: "genre", ICRD: "date", ICOP: "copyright" };
|
||||||
|
const ASF_CD_TO_COMMON = { title: "title", author: "artist", copyright: "copyright", description: "comment" };
|
||||||
|
|
||||||
|
/** Maps source object fields to the common tags layer via a mapping table. */
|
||||||
|
function mapCommon(report, source, mapping) {
|
||||||
|
const c = report.tags.common;
|
||||||
|
for (const [sk, ck] of Object.entries(mapping))
|
||||||
|
c[ck] = c[ck] || source[sk] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Maps an ID3v2 frame value to the common tags layer. */
|
||||||
|
function mapCommonId3(report, frameId, text) {
|
||||||
|
const field = ID3_TO_COMMON[frameId];
|
||||||
|
if (field) report.tags.common[field] = report.tags.common[field] || text || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Decodes an ID3v2 COMM (Comments) frame. */
|
||||||
|
function decodeCommFrame(data) {
|
||||||
|
if (!data || data.length < 5) return null;
|
||||||
|
const enc = data[0];
|
||||||
|
const language = String.fromCharCode(data[1], data[2], data[3]);
|
||||||
|
const { valueBytes: descBytes, next } = readNullTerminated(data, 4, enc);
|
||||||
|
const short_description = decodeText(descBytes, enc).replace(/\u0000/g, "").trim() || null;
|
||||||
|
const text = decodeText(data.slice(next), enc).replace(/\u0000/g, "").trim() || null;
|
||||||
|
return { language, short_description, text };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Normalizes TLEN to integer milliseconds. */
|
||||||
|
function normalizeTlen(s) {
|
||||||
|
if (!s) return null;
|
||||||
|
if (/^\s*\d+\s*$/.test(s)) return parseInt(s.trim(), 10);
|
||||||
|
const f = Number(s);
|
||||||
|
if (Number.isFinite(f) && f > 0 && f < 100000) return Math.round(f * 1000);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parses the ID3v2 tag header and frames. */
|
||||||
|
function parseId3v2(mp3) {
|
||||||
|
if (mp3.length < 10 || mp3[0] !== 0x49 || mp3[1] !== 0x44 || mp3[2] !== 0x33) return null;
|
||||||
|
|
||||||
|
const major = mp3[3], minor = mp3[4], flags = mp3[5];
|
||||||
|
const tagSize = synchsafeToInt(mp3[6], mp3[7], mp3[8], mp3[9]);
|
||||||
|
let offset = 10;
|
||||||
|
const end = 10 + tagSize;
|
||||||
|
|
||||||
|
const frames = [];
|
||||||
|
while (offset + 10 <= end) {
|
||||||
|
const id = String.fromCharCode(mp3[offset], mp3[offset + 1], mp3[offset + 2], mp3[offset + 3]);
|
||||||
|
if (!/^[A-Z0-9]{4}$/.test(id)) break;
|
||||||
|
const size = major === 4 ?
|
||||||
|
synchsafeToInt(mp3[offset + 4], mp3[offset + 5], mp3[offset + 6], mp3[offset + 7]) :
|
||||||
|
u32be(mp3, offset + 4);
|
||||||
|
offset += 10;
|
||||||
|
if (size <= 0 || offset + size > mp3.length) break;
|
||||||
|
frames.push({ id, size, data: mp3.slice(offset, offset + size) });
|
||||||
|
offset += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { header: { version: `${major}.${minor}`, flags, tag_size: tagSize }, frames };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parses the 128-byte ID3v1 tag at the end of the file. */
|
||||||
|
function parseId3v1(b) {
|
||||||
|
if (b.length < 128) return null;
|
||||||
|
const off = b.length - 128;
|
||||||
|
if (b[off] !== 0x54 || b[off + 1] !== 0x41 || b[off + 2] !== 0x47) return null;
|
||||||
|
|
||||||
|
let track = null;
|
||||||
|
if (b[off + 125] === 0x00 && b[off + 126] !== 0x00) track = String(b[off + 126]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: decodeLatin1Trim(b.slice(off + 3, off + 33)),
|
||||||
|
artist: decodeLatin1Trim(b.slice(off + 33, off + 63)),
|
||||||
|
album: decodeLatin1Trim(b.slice(off + 63, off + 93)),
|
||||||
|
year: decodeLatin1Trim(b.slice(off + 93, off + 97)),
|
||||||
|
comment: decodeLatin1Trim(b.slice(off + 97, off + 127)),
|
||||||
|
track, genre: String(b[off + 127]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Decodes an ID3v2 TXXX (user-defined text) frame. */
|
||||||
|
function decodeTxxx(data) {
|
||||||
|
if (!data || data.length < 2) return null;
|
||||||
|
const enc = data[0];
|
||||||
|
const { valueBytes: descBytes, next } = readNullTerminated(data, 1, enc);
|
||||||
|
const desc = decodeText(descBytes, enc).replace(/\u0000/g, "").trim();
|
||||||
|
const val = decodeText(data.slice(next), enc).replace(/\u0000/g, "").trim();
|
||||||
|
return { description: desc || null, value: val || null };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Best-effort APEv2 tag parser scanning the last 32 KB. */
|
||||||
|
function parseApeV2BestEffort(b) {
|
||||||
|
const scanStart = Math.max(0, b.length - 32768);
|
||||||
|
const idx = indexOfAscii(b, "APETAGEX", scanStart, b.length);
|
||||||
|
if (idx < 0) return null;
|
||||||
|
if (idx + 32 > b.length) return { present: true, warning: "APETAGEX found but footer truncated." };
|
||||||
|
|
||||||
|
const ver = u32le(b, idx + 8), size = u32le(b, idx + 12);
|
||||||
|
const count = u32le(b, idx + 16), flags = u32le(b, idx + 20);
|
||||||
|
|
||||||
|
const tagStart = idx + 32 - size;
|
||||||
|
if (tagStart < 0 || tagStart >= b.length)
|
||||||
|
return { present: true, version: ver, size, count, flags, warning: "APEv2 bounds invalid (non-standard placement)." };
|
||||||
|
|
||||||
|
const items = [];
|
||||||
|
let off = tagStart + 32;
|
||||||
|
const end = Math.min(b.length, idx);
|
||||||
|
while (off + 8 < end && items.length < 5000) {
|
||||||
|
const valueSize = u32le(b, off), itemFlags = u32le(b, off + 4);
|
||||||
|
off += 8;
|
||||||
|
let keyEnd = off;
|
||||||
|
while (keyEnd < end && b[keyEnd] !== 0x00) keyEnd++;
|
||||||
|
const key = decodeLatin1Trim(b.slice(off, keyEnd));
|
||||||
|
off = keyEnd + 1;
|
||||||
|
if (!key || off + valueSize > end) break;
|
||||||
|
const value = safeUtf8(b.slice(off, off + valueSize)).replace(/\u0000/g, "").trim();
|
||||||
|
off += valueSize;
|
||||||
|
items.push({ key, value, flags: itemFlags });
|
||||||
|
}
|
||||||
|
|
||||||
|
return { present: true, version: ver, size, count, flags, items };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Enumerates RIFF-style chunks (id + LE32 size) within a byte range, padding to even. */
|
||||||
|
function enumerateChunks(b, start, end, maxCount) {
|
||||||
|
const chunks = [];
|
||||||
|
let off = start;
|
||||||
|
while (off + 8 <= end && chunks.length < maxCount) {
|
||||||
|
const id = ascii4(b, off);
|
||||||
|
const size = u32le(b, off + 4);
|
||||||
|
const dataOff = off + 8;
|
||||||
|
if (dataOff + size > end) break;
|
||||||
|
chunks.push({ id, size, dataOff });
|
||||||
|
off = dataOff + size + (size % 2);
|
||||||
|
}
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parses a BWF bext chunk. */
|
||||||
|
function parseBext(b, off, size) {
|
||||||
|
const slice = b.slice(off, off + size);
|
||||||
|
const timeRefLow = u32le(slice, 338), timeRefHigh = u32le(slice, 342);
|
||||||
|
return {
|
||||||
|
description: decodeLatin1Trim(slice.slice(0, 256)) || null,
|
||||||
|
originator: decodeLatin1Trim(slice.slice(256, 288)) || null,
|
||||||
|
originator_reference: decodeLatin1Trim(slice.slice(288, 320)) || null,
|
||||||
|
origination_date: decodeLatin1Trim(slice.slice(320, 330)) || null,
|
||||||
|
origination_time: decodeLatin1Trim(slice.slice(330, 338)) || null,
|
||||||
|
time_reference_samples: ((BigInt(timeRefHigh) << 32n) | BigInt(timeRefLow)).toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const FLAC_TYPE_NAMES = { 0: "STREAMINFO", 1: "PADDING", 2: "APPLICATION", 3: "SEEKTABLE", 4: "VORBIS_COMMENT", 5: "CUESHEET", 6: "PICTURE" };
|
||||||
|
|
||||||
|
/** Parses FLAC metadata blocks following the "fLaC" marker. */
|
||||||
|
function parseFlacMetaBlocks(b) {
|
||||||
|
const blocks = [];
|
||||||
|
let off = 4;
|
||||||
|
while (off + 4 <= b.length && blocks.length < 10000) {
|
||||||
|
const header = b[off];
|
||||||
|
const isLast = (header & 0x80) !== 0;
|
||||||
|
const type = header & 0x7f;
|
||||||
|
const len = (b[off + 1] << 16) | (b[off + 2] << 8) | b[off + 3];
|
||||||
|
off += 4;
|
||||||
|
if (off + len > b.length) break;
|
||||||
|
blocks.push({ type, typeName: FLAC_TYPE_NAMES[type] || `TYPE_${type}`, length: len, data: b.slice(off, off + len) });
|
||||||
|
off += len;
|
||||||
|
if (isLast) break;
|
||||||
|
}
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parses a Vorbis Comment block (used by FLAC and OGG). */
|
||||||
|
function parseVorbisComment(buf) {
|
||||||
|
let off = 0;
|
||||||
|
const vendorLen = u32le(buf, off); off += 4;
|
||||||
|
if (off + vendorLen > buf.length) return { vendor: null, comments: [], warning: "vendor_len out of bounds" };
|
||||||
|
const vendor = safeUtf8(buf.slice(off, off + vendorLen)); off += vendorLen;
|
||||||
|
const count = u32le(buf, off); off += 4;
|
||||||
|
|
||||||
|
const comments = [];
|
||||||
|
for (let i = 0; i < count && off + 4 <= buf.length && comments.length < 20000; i++) {
|
||||||
|
const l = u32le(buf, off); off += 4;
|
||||||
|
if (off + l > buf.length) break;
|
||||||
|
const s = safeUtf8(buf.slice(off, off + l)); off += l;
|
||||||
|
const eq = s.indexOf("=");
|
||||||
|
if (eq > 0) comments.push({ key: s.slice(0, eq).toUpperCase(), value: s.slice(eq + 1) });
|
||||||
|
}
|
||||||
|
return { vendor, comments };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Maps Vorbis Comment fields to the common tags layer. */
|
||||||
|
function mapVorbisCommon(report, vc) {
|
||||||
|
const c = report.tags.common;
|
||||||
|
for (const [vk, ck] of Object.entries(VORBIS_TO_COMMON))
|
||||||
|
c[ck] = c[ck] || vc.comments?.find((x) => x.key === vk)?.value || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parses a FLAC PICTURE metadata block (extracts mime, description, data length). */
|
||||||
|
function parseFlacPicture(data, maxTextBytes) {
|
||||||
|
let off = 4;
|
||||||
|
const mimeLen = u32be(data, off); off += 4;
|
||||||
|
const mime = safeUtf8(data.slice(off, off + Math.min(mimeLen, maxTextBytes))); off += mimeLen;
|
||||||
|
const descLen = u32be(data, off); off += 4;
|
||||||
|
const description = safeUtf8(data.slice(off, off + Math.min(descLen, maxTextBytes))); off += descLen + 16;
|
||||||
|
return { mime, description, dataLength: u32be(data, off) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parses the ASF Content Description Object fields. */
|
||||||
|
function parseAsfContentDescription(b, off) {
|
||||||
|
const titleLen = u16le(b, off), authorLen = u16le(b, off + 2);
|
||||||
|
const copyrightLen = u16le(b, off + 4), descLen = u16le(b, off + 6), ratingLen = u16le(b, off + 8);
|
||||||
|
let pos = off + 10;
|
||||||
|
const title = decodeUtf16LE(b, pos, titleLen); pos += titleLen;
|
||||||
|
const author = decodeUtf16LE(b, pos, authorLen); pos += authorLen;
|
||||||
|
const copyright = decodeUtf16LE(b, pos, copyrightLen); pos += copyrightLen;
|
||||||
|
const description = decodeUtf16LE(b, pos, descLen); pos += descLen;
|
||||||
|
const rating = decodeUtf16LE(b, pos, ratingLen);
|
||||||
|
return { title, author, copyright, description, rating };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parses the ASF Extended Content Description Object descriptors. */
|
||||||
|
function parseAsfExtContentDescription(b, off, end) {
|
||||||
|
const count = u16le(b, off);
|
||||||
|
let pos = off + 2;
|
||||||
|
const descriptors = [];
|
||||||
|
for (let i = 0; i < count && pos + 6 <= end && descriptors.length < 5000; i++) {
|
||||||
|
const nameLen = u16le(b, pos); pos += 2;
|
||||||
|
if (pos + nameLen > end) break;
|
||||||
|
const name = decodeUtf16LE(b, pos, nameLen); pos += nameLen;
|
||||||
|
const valueType = u16le(b, pos); pos += 2;
|
||||||
|
const valueLen = u16le(b, pos); pos += 2;
|
||||||
|
if (pos + valueLen > end) break;
|
||||||
|
let value;
|
||||||
|
if (valueType === 0) value = decodeUtf16LE(b, pos, valueLen);
|
||||||
|
else if (valueType === 3) value = u32le(b, pos);
|
||||||
|
else if (valueType === 5) value = u16le(b, pos);
|
||||||
|
else if (valueType === 2) value = u32le(b, pos) !== 0;
|
||||||
|
else value = `(${valueLen} bytes, type ${valueType})`;
|
||||||
|
pos += valueLen;
|
||||||
|
descriptors.push({ name, value_type: valueType, value });
|
||||||
|
}
|
||||||
|
return descriptors;
|
||||||
|
}
|
||||||
73
src/core/lib/BigIntUtils.mjs
Normal file
73
src/core/lib/BigIntUtils.mjs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
/**
|
||||||
|
* @author p-leriche [philip.leriche@cantab.net]
|
||||||
|
* @copyright Crown Copyright 2025
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number theory utilities used by cryptographic operations.
|
||||||
|
*
|
||||||
|
* Currently provides:
|
||||||
|
* - parseBigInt
|
||||||
|
* - Extended Euclidean Algorithm
|
||||||
|
* - Modular Exponentiation
|
||||||
|
*
|
||||||
|
* Additional algorithms may be added as required.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* parseBigInt helper operation
|
||||||
|
*/
|
||||||
|
export function parseBigInt(value, param) {
|
||||||
|
const v = (value ?? "").trim();
|
||||||
|
if (/^0x[0-9a-f]+$/i.test(v)) return BigInt(v);
|
||||||
|
if (/^[+-]?[0-9]+$/.test(v)) return BigInt(v);
|
||||||
|
throw new OperationError(param + " must be decimal or hex (0x...)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extended Euclidean Algorithm
|
||||||
|
*
|
||||||
|
* Returns [g, x, y] such that:
|
||||||
|
* a*x + b*y = g = gcd(a, b)
|
||||||
|
*
|
||||||
|
* (Uses an iterative algorithm to avoid possible stack overflow)
|
||||||
|
*/
|
||||||
|
export function egcd(a, b) {
|
||||||
|
let oldR = a, r = b;
|
||||||
|
let oldS = 1n, s = 0n;
|
||||||
|
let oldT = 0n, t = 1n;
|
||||||
|
|
||||||
|
while (r !== 0n) {
|
||||||
|
const quotient = oldR / r;
|
||||||
|
|
||||||
|
[oldR, r] = [r, oldR - quotient * r];
|
||||||
|
[oldS, s] = [s, oldS - quotient * s];
|
||||||
|
[oldT, t] = [t, oldT - quotient * t];
|
||||||
|
}
|
||||||
|
|
||||||
|
// oldR is the gcd
|
||||||
|
// oldS and oldT are the Bézout coefficients
|
||||||
|
return [oldR, oldS, oldT];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modular exponentiation
|
||||||
|
*/
|
||||||
|
export function modPow(base, exponent, modulus) {
|
||||||
|
let result = 1n;
|
||||||
|
base %= modulus;
|
||||||
|
|
||||||
|
while (exponent > 0n) {
|
||||||
|
if (exponent & 1n) {
|
||||||
|
result = (result * base) % modulus;
|
||||||
|
}
|
||||||
|
base = (base * base) % modulus;
|
||||||
|
exponent >>= 1n;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@ -45,6 +45,11 @@ export function search(input, searchRegex, removeRegex=null, sortBy=null, unique
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Email regular expression
|
||||||
|
*/
|
||||||
|
export const EMAIL_REGEX = /(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?\.)+[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\])/ig;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* URL regular expression
|
* URL regular expression
|
||||||
|
|||||||
@ -33,7 +33,7 @@ export function toHex(data, delim=" ", padding=2, extraDelim="", lineSize=0) {
|
|||||||
if (data instanceof ArrayBuffer) data = new Uint8Array(data);
|
if (data instanceof ArrayBuffer) data = new Uint8Array(data);
|
||||||
|
|
||||||
let output = "";
|
let output = "";
|
||||||
const prepend = (delim === "0x" || delim === "\\x");
|
const prepend = (delim === "0x" || delim === "\\x" || delim === "%");
|
||||||
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
for (let i = 0; i < data.length; i++) {
|
||||||
const hex = data[i].toString(16).padStart(padding, "0");
|
const hex = data[i].toString(16).padStart(padding, "0");
|
||||||
|
|||||||
@ -1,251 +0,0 @@
|
|||||||
/**
|
|
||||||
* Image manipulation resources
|
|
||||||
*
|
|
||||||
* @author j433866 [j433866@gmail.com]
|
|
||||||
* @copyright Crown Copyright 2019
|
|
||||||
* @license Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
import OperationError from "../errors/OperationError.mjs";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gaussian blurs an image.
|
|
||||||
*
|
|
||||||
* @param {jimp} input
|
|
||||||
* @param {number} radius
|
|
||||||
* @param {boolean} fast
|
|
||||||
* @returns {jimp}
|
|
||||||
*/
|
|
||||||
export function gaussianBlur (input, radius) {
|
|
||||||
try {
|
|
||||||
// From http://blog.ivank.net/fastest-gaussian-blur.html
|
|
||||||
const boxes = boxesForGauss(radius, 3);
|
|
||||||
for (let i = 0; i < 3; i++) {
|
|
||||||
input = boxBlur(input, (boxes[i] - 1) / 2);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
throw new OperationError(`Error blurring image. (${err})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {number} radius
|
|
||||||
* @param {number} numBoxes
|
|
||||||
* @returns {Array}
|
|
||||||
*/
|
|
||||||
function boxesForGauss(radius, numBoxes) {
|
|
||||||
const idealWidth = Math.sqrt((12 * radius * radius / numBoxes) + 1);
|
|
||||||
|
|
||||||
let wl = Math.floor(idealWidth);
|
|
||||||
|
|
||||||
if (wl % 2 === 0) {
|
|
||||||
wl--;
|
|
||||||
}
|
|
||||||
|
|
||||||
const wu = wl + 2;
|
|
||||||
|
|
||||||
const mIdeal = (12 * radius * radius - numBoxes * wl * wl - 4 * numBoxes * wl - 3 * numBoxes) / (-4 * wl - 4);
|
|
||||||
const m = Math.round(mIdeal);
|
|
||||||
|
|
||||||
const sizes = [];
|
|
||||||
for (let i = 0; i < numBoxes; i++) {
|
|
||||||
sizes.push(i < m ? wl : wu);
|
|
||||||
}
|
|
||||||
return sizes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies a box blur effect to the image
|
|
||||||
*
|
|
||||||
* @param {jimp} source
|
|
||||||
* @param {number} radius
|
|
||||||
* @returns {jimp}
|
|
||||||
*/
|
|
||||||
function boxBlur (source, radius) {
|
|
||||||
const width = source.bitmap.width;
|
|
||||||
const height = source.bitmap.height;
|
|
||||||
let output = source.clone();
|
|
||||||
output = boxBlurH(source, output, width, height, radius);
|
|
||||||
source = boxBlurV(output, source, width, height, radius);
|
|
||||||
|
|
||||||
return source;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies the horizontal blur
|
|
||||||
*
|
|
||||||
* @param {jimp} source
|
|
||||||
* @param {jimp} output
|
|
||||||
* @param {number} width
|
|
||||||
* @param {number} height
|
|
||||||
* @param {number} radius
|
|
||||||
* @returns {jimp}
|
|
||||||
*/
|
|
||||||
function boxBlurH (source, output, width, height, radius) {
|
|
||||||
const iarr = 1 / (radius + radius + 1);
|
|
||||||
for (let i = 0; i < height; i++) {
|
|
||||||
let ti = 0,
|
|
||||||
li = ti,
|
|
||||||
ri = ti + radius;
|
|
||||||
const idx = source.getPixelIndex(ti, i);
|
|
||||||
const firstValRed = source.bitmap.data[idx],
|
|
||||||
firstValGreen = source.bitmap.data[idx + 1],
|
|
||||||
firstValBlue = source.bitmap.data[idx + 2],
|
|
||||||
firstValAlpha = source.bitmap.data[idx + 3];
|
|
||||||
|
|
||||||
const lastIdx = source.getPixelIndex(width - 1, i),
|
|
||||||
lastValRed = source.bitmap.data[lastIdx],
|
|
||||||
lastValGreen = source.bitmap.data[lastIdx + 1],
|
|
||||||
lastValBlue = source.bitmap.data[lastIdx + 2],
|
|
||||||
lastValAlpha = source.bitmap.data[lastIdx + 3];
|
|
||||||
|
|
||||||
let red = (radius + 1) * firstValRed;
|
|
||||||
let green = (radius + 1) * firstValGreen;
|
|
||||||
let blue = (radius + 1) * firstValBlue;
|
|
||||||
let alpha = (radius + 1) * firstValAlpha;
|
|
||||||
|
|
||||||
for (let j = 0; j < radius; j++) {
|
|
||||||
const jIdx = source.getPixelIndex(ti + j, i);
|
|
||||||
red += source.bitmap.data[jIdx];
|
|
||||||
green += source.bitmap.data[jIdx + 1];
|
|
||||||
blue += source.bitmap.data[jIdx + 2];
|
|
||||||
alpha += source.bitmap.data[jIdx + 3];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let j = 0; j <= radius; j++) {
|
|
||||||
const jIdx = source.getPixelIndex(ri++, i);
|
|
||||||
red += source.bitmap.data[jIdx] - firstValRed;
|
|
||||||
green += source.bitmap.data[jIdx + 1] - firstValGreen;
|
|
||||||
blue += source.bitmap.data[jIdx + 2] - firstValBlue;
|
|
||||||
alpha += source.bitmap.data[jIdx + 3] - firstValAlpha;
|
|
||||||
|
|
||||||
const tiIdx = source.getPixelIndex(ti++, i);
|
|
||||||
output.bitmap.data[tiIdx] = Math.round(red * iarr);
|
|
||||||
output.bitmap.data[tiIdx + 1] = Math.round(green * iarr);
|
|
||||||
output.bitmap.data[tiIdx + 2] = Math.round(blue * iarr);
|
|
||||||
output.bitmap.data[tiIdx + 3] = Math.round(alpha * iarr);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let j = radius + 1; j < width - radius; j++) {
|
|
||||||
const riIdx = source.getPixelIndex(ri++, i);
|
|
||||||
const liIdx = source.getPixelIndex(li++, i);
|
|
||||||
red += source.bitmap.data[riIdx] - source.bitmap.data[liIdx];
|
|
||||||
green += source.bitmap.data[riIdx + 1] - source.bitmap.data[liIdx + 1];
|
|
||||||
blue += source.bitmap.data[riIdx + 2] - source.bitmap.data[liIdx + 2];
|
|
||||||
alpha += source.bitmap.data[riIdx + 3] - source.bitmap.data[liIdx + 3];
|
|
||||||
|
|
||||||
const tiIdx = source.getPixelIndex(ti++, i);
|
|
||||||
output.bitmap.data[tiIdx] = Math.round(red * iarr);
|
|
||||||
output.bitmap.data[tiIdx + 1] = Math.round(green * iarr);
|
|
||||||
output.bitmap.data[tiIdx + 2] = Math.round(blue * iarr);
|
|
||||||
output.bitmap.data[tiIdx + 3] = Math.round(alpha * iarr);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let j = width - radius; j < width; j++) {
|
|
||||||
const liIdx = source.getPixelIndex(li++, i);
|
|
||||||
red += lastValRed - source.bitmap.data[liIdx];
|
|
||||||
green += lastValGreen - source.bitmap.data[liIdx + 1];
|
|
||||||
blue += lastValBlue - source.bitmap.data[liIdx + 2];
|
|
||||||
alpha += lastValAlpha - source.bitmap.data[liIdx + 3];
|
|
||||||
|
|
||||||
const tiIdx = source.getPixelIndex(ti++, i);
|
|
||||||
output.bitmap.data[tiIdx] = Math.round(red * iarr);
|
|
||||||
output.bitmap.data[tiIdx + 1] = Math.round(green * iarr);
|
|
||||||
output.bitmap.data[tiIdx + 2] = Math.round(blue * iarr);
|
|
||||||
output.bitmap.data[tiIdx + 3] = Math.round(alpha * iarr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies the vertical blur
|
|
||||||
*
|
|
||||||
* @param {jimp} source
|
|
||||||
* @param {jimp} output
|
|
||||||
* @param {number} width
|
|
||||||
* @param {number} height
|
|
||||||
* @param {number} radius
|
|
||||||
* @returns {jimp}
|
|
||||||
*/
|
|
||||||
function boxBlurV (source, output, width, height, radius) {
|
|
||||||
const iarr = 1 / (radius + radius + 1);
|
|
||||||
for (let i = 0; i < width; i++) {
|
|
||||||
let ti = 0,
|
|
||||||
li = ti,
|
|
||||||
ri = ti + radius;
|
|
||||||
|
|
||||||
const idx = source.getPixelIndex(i, ti);
|
|
||||||
|
|
||||||
const firstValRed = source.bitmap.data[idx],
|
|
||||||
firstValGreen = source.bitmap.data[idx + 1],
|
|
||||||
firstValBlue = source.bitmap.data[idx + 2],
|
|
||||||
firstValAlpha = source.bitmap.data[idx + 3];
|
|
||||||
|
|
||||||
const lastIdx = source.getPixelIndex(i, height - 1),
|
|
||||||
lastValRed = source.bitmap.data[lastIdx],
|
|
||||||
lastValGreen = source.bitmap.data[lastIdx + 1],
|
|
||||||
lastValBlue = source.bitmap.data[lastIdx + 2],
|
|
||||||
lastValAlpha = source.bitmap.data[lastIdx + 3];
|
|
||||||
|
|
||||||
let red = (radius + 1) * firstValRed;
|
|
||||||
let green = (radius + 1) * firstValGreen;
|
|
||||||
let blue = (radius + 1) * firstValBlue;
|
|
||||||
let alpha = (radius + 1) * firstValAlpha;
|
|
||||||
|
|
||||||
for (let j = 0; j < radius; j++) {
|
|
||||||
const jIdx = source.getPixelIndex(i, ti + j);
|
|
||||||
red += source.bitmap.data[jIdx];
|
|
||||||
green += source.bitmap.data[jIdx + 1];
|
|
||||||
blue += source.bitmap.data[jIdx + 2];
|
|
||||||
alpha += source.bitmap.data[jIdx + 3];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let j = 0; j <= radius; j++) {
|
|
||||||
const riIdx = source.getPixelIndex(i, ri++);
|
|
||||||
red += source.bitmap.data[riIdx] - firstValRed;
|
|
||||||
green += source.bitmap.data[riIdx + 1] - firstValGreen;
|
|
||||||
blue += source.bitmap.data[riIdx + 2] - firstValBlue;
|
|
||||||
alpha += source.bitmap.data[riIdx + 3] - firstValAlpha;
|
|
||||||
|
|
||||||
const tiIdx = source.getPixelIndex(i, ti++);
|
|
||||||
output.bitmap.data[tiIdx] = Math.round(red * iarr);
|
|
||||||
output.bitmap.data[tiIdx + 1] = Math.round(green * iarr);
|
|
||||||
output.bitmap.data[tiIdx + 2] = Math.round(blue * iarr);
|
|
||||||
output.bitmap.data[tiIdx + 3] = Math.round(alpha * iarr);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let j = radius + 1; j < height - radius; j++) {
|
|
||||||
const riIdx = source.getPixelIndex(i, ri++);
|
|
||||||
const liIdx = source.getPixelIndex(i, li++);
|
|
||||||
red += source.bitmap.data[riIdx] - source.bitmap.data[liIdx];
|
|
||||||
green += source.bitmap.data[riIdx + 1] - source.bitmap.data[liIdx + 1];
|
|
||||||
blue += source.bitmap.data[riIdx + 2] - source.bitmap.data[liIdx + 2];
|
|
||||||
alpha += source.bitmap.data[riIdx + 3] - source.bitmap.data[liIdx + 3];
|
|
||||||
|
|
||||||
const tiIdx = source.getPixelIndex(i, ti++);
|
|
||||||
output.bitmap.data[tiIdx] = Math.round(red * iarr);
|
|
||||||
output.bitmap.data[tiIdx + 1] = Math.round(green * iarr);
|
|
||||||
output.bitmap.data[tiIdx + 2] = Math.round(blue * iarr);
|
|
||||||
output.bitmap.data[tiIdx + 3] = Math.round(alpha * iarr);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let j = height - radius; j < height; j++) {
|
|
||||||
const liIdx = source.getPixelIndex(i, li++);
|
|
||||||
red += lastValRed - source.bitmap.data[liIdx];
|
|
||||||
green += lastValGreen - source.bitmap.data[liIdx + 1];
|
|
||||||
blue += lastValBlue - source.bitmap.data[liIdx + 2];
|
|
||||||
alpha += lastValAlpha - source.bitmap.data[liIdx + 3];
|
|
||||||
|
|
||||||
const tiIdx = source.getPixelIndex(i, ti++);
|
|
||||||
output.bitmap.data[tiIdx] = Math.round(red * iarr);
|
|
||||||
output.bitmap.data[tiIdx + 1] = Math.round(green * iarr);
|
|
||||||
output.bitmap.data[tiIdx + 2] = Math.round(blue * iarr);
|
|
||||||
output.bitmap.data[tiIdx + 3] = Math.round(alpha * iarr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
@ -50,6 +50,7 @@ const HEX_ALPHABET_MAP = HEX_ALPHABET.split("");
|
|||||||
export function toModhex(data, delim=" ", padding=2, extraDelim="", lineSize=0) {
|
export function toModhex(data, delim=" ", padding=2, extraDelim="", lineSize=0) {
|
||||||
if (!data) return "";
|
if (!data) return "";
|
||||||
if (data instanceof ArrayBuffer) data = new Uint8Array(data);
|
if (data instanceof ArrayBuffer) data = new Uint8Array(data);
|
||||||
|
if (data.length === 0) return "";
|
||||||
|
|
||||||
const regularHexString = toHex(data, "", padding, "", 0);
|
const regularHexString = toHex(data, "", padding, "", 0);
|
||||||
|
|
||||||
@ -100,6 +101,7 @@ export function toModhex(data, delim=" ", padding=2, extraDelim="", lineSize=0)
|
|||||||
export function toModhexFast(data) {
|
export function toModhexFast(data) {
|
||||||
if (!data) return "";
|
if (!data) return "";
|
||||||
if (data instanceof ArrayBuffer) data = new Uint8Array(data);
|
if (data instanceof ArrayBuffer) data = new Uint8Array(data);
|
||||||
|
if (data.length === 0) return "";
|
||||||
|
|
||||||
const output = [];
|
const output = [];
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import OperationError from "../errors/OperationError.mjs";
|
|||||||
import jsQR from "jsqr";
|
import jsQR from "jsqr";
|
||||||
import qr from "qr-image";
|
import qr from "qr-image";
|
||||||
import Utils from "../Utils.mjs";
|
import Utils from "../Utils.mjs";
|
||||||
import Jimp from "jimp/es/index.js";
|
import { Jimp, JimpMime } from "jimp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a QR code image from an image
|
* Parses a QR code image from an image
|
||||||
@ -29,18 +29,31 @@ export async function parseQrCode(input, normalise) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (normalise) {
|
if (normalise) {
|
||||||
image.rgba(false);
|
|
||||||
image.background(0xFFFFFFFF);
|
|
||||||
image.normalize();
|
|
||||||
image.greyscale();
|
image.greyscale();
|
||||||
image = await image.getBufferAsync(Jimp.MIME_JPEG);
|
image.normalize();
|
||||||
image = await Jimp.read(image);
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error normalising image. (${err})`);
|
throw new OperationError(`Error normalising image. (${err})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const qrData = jsQR(image.bitmap.data, image.getWidth(), image.getHeight());
|
// Remove transparency which jsQR cannot handle
|
||||||
|
image.scan((x, y, idx) => {
|
||||||
|
// If pixel is fully transparent, make it opaque white
|
||||||
|
if (image.bitmap.data[idx + 3] === 0x00) {
|
||||||
|
image.bitmap.data[idx + 0] = 0xff;
|
||||||
|
image.bitmap.data[idx + 1] = 0xff;
|
||||||
|
image.bitmap.data[idx + 2] = 0xff;
|
||||||
|
}
|
||||||
|
// Otherwise, make it fully opaque at its existing colour
|
||||||
|
image.bitmap.data[idx + 3] = 0xff;
|
||||||
|
});
|
||||||
|
image = await Jimp.read(await image.getBuffer(JimpMime.jpeg));
|
||||||
|
|
||||||
|
const qrData = jsQR(
|
||||||
|
new Uint8ClampedArray(image.bitmap.data),
|
||||||
|
image.width,
|
||||||
|
image.height,
|
||||||
|
);
|
||||||
if (qrData) {
|
if (qrData) {
|
||||||
return qrData.data;
|
return qrData.data;
|
||||||
} else {
|
} else {
|
||||||
@ -58,7 +71,13 @@ export async function parseQrCode(input, normalise) {
|
|||||||
* @param {string} errorCorrection
|
* @param {string} errorCorrection
|
||||||
* @returns {ArrayBuffer}
|
* @returns {ArrayBuffer}
|
||||||
*/
|
*/
|
||||||
export function generateQrCode(input, format, moduleSize, margin, errorCorrection) {
|
export function generateQrCode(
|
||||||
|
input,
|
||||||
|
format,
|
||||||
|
moduleSize,
|
||||||
|
margin,
|
||||||
|
errorCorrection,
|
||||||
|
) {
|
||||||
const formats = ["SVG", "EPS", "PDF", "PNG"];
|
const formats = ["SVG", "EPS", "PDF", "PNG"];
|
||||||
if (!formats.includes(format.toUpperCase())) {
|
if (!formats.includes(format.toUpperCase())) {
|
||||||
throw new OperationError("Unsupported QR code format.");
|
throw new OperationError("Unsupported QR code format.");
|
||||||
@ -70,7 +89,8 @@ export function generateQrCode(input, format, moduleSize, margin, errorCorrectio
|
|||||||
type: format,
|
type: format,
|
||||||
size: moduleSize,
|
size: moduleSize,
|
||||||
margin: margin,
|
margin: margin,
|
||||||
"ec_level": errorCorrection.charAt(0).toUpperCase()
|
// eslint-disable-next-line camelcase
|
||||||
|
ec_level: errorCorrection.charAt(0).toUpperCase(),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error generating QR code. (${err})`);
|
throw new OperationError(`Error generating QR code. (${err})`);
|
||||||
@ -86,7 +106,7 @@ export function generateQrCode(input, format, moduleSize, margin, errorCorrectio
|
|||||||
case "PDF":
|
case "PDF":
|
||||||
return Utils.strToArrayBuffer(qrImage);
|
return Utils.strToArrayBuffer(qrImage);
|
||||||
case "PNG":
|
case "PNG":
|
||||||
return qrImage.buffer;
|
return qrImage.buffer.slice(qrImage.byteOffset, qrImage.byteLength + qrImage.byteOffset);
|
||||||
default:
|
default:
|
||||||
throw new OperationError("Unsupported QR code format.");
|
throw new OperationError("Unsupported QR code format.");
|
||||||
}
|
}
|
||||||
|
|||||||
625
src/core/lib/RC6.mjs
Normal file
625
src/core/lib/RC6.mjs
Normal file
@ -0,0 +1,625 @@
|
|||||||
|
/**
|
||||||
|
* Complete implementation of RC6 block cipher encryption/decryption with
|
||||||
|
* configurable word size (w), rounds (r), and key length (b).
|
||||||
|
*
|
||||||
|
* RC6 was an AES finalist designed by Ron Rivest, Matt Robshaw, Ray Sidney, and Yiqun Lisa Yin.
|
||||||
|
* Reference: https://en.wikipedia.org/wiki/RC6
|
||||||
|
* Test Vectors: https://datatracker.ietf.org/doc/html/draft-krovetz-rc6-rc5-vectors-00
|
||||||
|
*
|
||||||
|
* The P and Q constants are derived from mathematical constants e (Euler's number) and
|
||||||
|
* φ (golden ratio) as specified in the IETF draft. Master 256-bit values are scaled to
|
||||||
|
* any word size.
|
||||||
|
*
|
||||||
|
* @author Medjedtxm
|
||||||
|
* @copyright Crown Copyright 2026
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Master P constant (256-bit) from IETF draft-krovetz-rc6-rc5-vectors-00
|
||||||
|
* Derived from Odd((e-2) * 2^256) where e = 2.71828...
|
||||||
|
*/
|
||||||
|
const P_256 = 0xb7e151628aed2a6abf7158809cf4f3c762e7160f38b4da56a784d9045190cfefn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Master Q constant (256-bit) from IETF draft-krovetz-rc6-rc5-vectors-00
|
||||||
|
* Derived from Odd((φ-1) * 2^256) where φ = 1.61803... (golden ratio)
|
||||||
|
*/
|
||||||
|
const Q_256 = 0x9e3779b97f4a7c15f39cc0605cedc8341082276bf3a27251f86c6a11d0c18e95n;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get P constant for given word size by scaling the 256-bit master constant
|
||||||
|
* @param {number} w - Word size in bits
|
||||||
|
* @returns {bigint} - P constant for word size w
|
||||||
|
*/
|
||||||
|
function getP(w) {
|
||||||
|
return (P_256 >> BigInt(256 - w)) | 1n; // Ensure odd
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Q constant for given word size by scaling the 256-bit master constant
|
||||||
|
* @param {number} w - Word size in bits
|
||||||
|
* @returns {bigint} - Q constant for word size w
|
||||||
|
*/
|
||||||
|
function getQ(w) {
|
||||||
|
return (Q_256 >> BigInt(256 - w)) | 1n; // Ensure odd
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get block size in bytes for given word size
|
||||||
|
* Block size = 4 words = 4 * (w/8) bytes
|
||||||
|
* @param {number} w - Word size in bits
|
||||||
|
* @returns {number} - Block size in bytes
|
||||||
|
*/
|
||||||
|
export function getBlockSize(w) {
|
||||||
|
return 4 * (w / 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get recommended number of rounds for given word size
|
||||||
|
* @param {number} w - Word size in bits
|
||||||
|
* @returns {number} - Recommended rounds
|
||||||
|
*/
|
||||||
|
export function getDefaultRounds(w) {
|
||||||
|
if (w <= 16) return 16;
|
||||||
|
if (w <= 32) return 20;
|
||||||
|
if (w <= 64) return 24;
|
||||||
|
return 28;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create mask for w-bit word
|
||||||
|
* @param {number} w - Word size in bits
|
||||||
|
* @returns {bigint} - Mask with w bits set
|
||||||
|
*/
|
||||||
|
function wordMask(w) {
|
||||||
|
return (1n << BigInt(w)) - 1n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotate left for arbitrary word size using BigInt
|
||||||
|
* Uses lower lg(w) bits of n for rotation amount (RC6 spec)
|
||||||
|
* @param {bigint} x - Value to rotate
|
||||||
|
* @param {bigint} n - Rotation amount
|
||||||
|
* @param {number} w - Word size in bits
|
||||||
|
* @param {bigint} lgMask - Mask for lower lg(w) bits
|
||||||
|
* @returns {bigint} - Rotated value
|
||||||
|
*/
|
||||||
|
function ROL(x, n, w, lgMask) {
|
||||||
|
const mask = wordMask(w);
|
||||||
|
// Mask to lg(w) bits, then mod w for non-power-of-2 word sizes
|
||||||
|
// For power-of-2, (n & lgMask) < w always, so mod w is no-op
|
||||||
|
const shift = (n & lgMask) % BigInt(w);
|
||||||
|
return ((x << shift) | (x >> (BigInt(w) - shift))) & mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotate right for arbitrary word size using BigInt
|
||||||
|
* Uses lower lg(w) bits of n for rotation amount (RC6 spec)
|
||||||
|
* @param {bigint} x - Value to rotate
|
||||||
|
* @param {bigint} n - Rotation amount
|
||||||
|
* @param {number} w - Word size in bits
|
||||||
|
* @param {bigint} lgMask - Mask for lower lg(w) bits
|
||||||
|
* @returns {bigint} - Rotated value
|
||||||
|
*/
|
||||||
|
function ROR(x, n, w, lgMask) {
|
||||||
|
const mask = wordMask(w);
|
||||||
|
// Mask to lg(w) bits, then mod w for non-power-of-2 word sizes
|
||||||
|
// For power-of-2, (n & lgMask) < w always, so mod w is no-op
|
||||||
|
const shift = (n & lgMask) % BigInt(w);
|
||||||
|
return ((x >> shift) | (x << (BigInt(w) - shift))) & mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert byte array to word array (little-endian) using BigInt
|
||||||
|
* @param {number[]} bytes - Input byte array
|
||||||
|
* @param {number} w - Word size in bits
|
||||||
|
* @returns {bigint[]} - Array of w-bit words as BigInt
|
||||||
|
*/
|
||||||
|
function bytesToWords(bytes, w) {
|
||||||
|
const bytesPerWord = w / 8;
|
||||||
|
const words = [];
|
||||||
|
for (let i = 0; i < bytes.length; i += bytesPerWord) {
|
||||||
|
let word = 0n;
|
||||||
|
for (let j = 0; j < bytesPerWord && (i + j) < bytes.length; j++) {
|
||||||
|
word |= BigInt(bytes[i + j] || 0) << BigInt(j * 8);
|
||||||
|
}
|
||||||
|
words.push(word);
|
||||||
|
}
|
||||||
|
return words;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert word array to byte array (little-endian) using BigInt
|
||||||
|
* @param {bigint[]} words - Array of words
|
||||||
|
* @param {number} w - Word size in bits
|
||||||
|
* @returns {number[]} - Output byte array
|
||||||
|
*/
|
||||||
|
function wordsToBytes(words, w) {
|
||||||
|
const bytesPerWord = w / 8;
|
||||||
|
const bytes = [];
|
||||||
|
for (const word of words) {
|
||||||
|
for (let j = 0; j < bytesPerWord; j++) {
|
||||||
|
bytes.push(Number((word >> BigInt(j * 8)) & 0xFFn));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate round subkeys from user key
|
||||||
|
*
|
||||||
|
* @param {number[]} key - User key as byte array
|
||||||
|
* @param {number} rounds - Number of rounds
|
||||||
|
* @param {number} w - Word size in bits
|
||||||
|
* @returns {bigint[]} - Array of 2r+4 subkeys as BigInt
|
||||||
|
*/
|
||||||
|
function generateSubkeys(key, rounds, w) {
|
||||||
|
const bytesPerWord = w / 8;
|
||||||
|
const b = key.length;
|
||||||
|
const c = Math.max(Math.ceil(b / bytesPerWord), 1);
|
||||||
|
|
||||||
|
// Convert key bytes to words, pad with zeros if needed
|
||||||
|
const paddedKey = [...key];
|
||||||
|
while (paddedKey.length < c * bytesPerWord) {
|
||||||
|
paddedKey.push(0);
|
||||||
|
}
|
||||||
|
const L = bytesToWords(paddedKey, w);
|
||||||
|
|
||||||
|
// Number of subkeys: 2*r + 4
|
||||||
|
const t = 2 * rounds + 4;
|
||||||
|
|
||||||
|
// Get P and Q for this word size
|
||||||
|
const P = getP(w);
|
||||||
|
const Q = getQ(w);
|
||||||
|
const mask = wordMask(w);
|
||||||
|
|
||||||
|
// lg(w) mask for rotation amounts (floor of log2(w), per RC6 spec)
|
||||||
|
const lgw = Math.floor(Math.log2(w));
|
||||||
|
const lgMask = (1n << BigInt(lgw)) - 1n;
|
||||||
|
|
||||||
|
// Initialise S array with magic constants
|
||||||
|
const S = new Array(t);
|
||||||
|
S[0] = P;
|
||||||
|
for (let i = 1; i < t; i++) {
|
||||||
|
S[i] = (S[i - 1] + Q) & mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mix key into S
|
||||||
|
let A = 0n, B = 0n;
|
||||||
|
let i = 0, j = 0;
|
||||||
|
const v = 3 * Math.max(c, t);
|
||||||
|
|
||||||
|
for (let s = 0; s < v; s++) {
|
||||||
|
A = S[i] = ROL((S[i] + A + B) & mask, 3n, w, lgMask);
|
||||||
|
B = L[j] = ROL((L[j] + A + B) & mask, A + B, w, lgMask);
|
||||||
|
i = (i + 1) % t;
|
||||||
|
j = (j + 1) % c;
|
||||||
|
}
|
||||||
|
|
||||||
|
return S;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt a single block using RC6
|
||||||
|
*
|
||||||
|
* @param {number[]} block - Plaintext block (4*w/8 bytes)
|
||||||
|
* @param {bigint[]} S - Subkeys array
|
||||||
|
* @param {number} rounds - Number of rounds
|
||||||
|
* @param {number} w - Word size in bits
|
||||||
|
* @returns {number[]} - Ciphertext block
|
||||||
|
*/
|
||||||
|
function encryptBlock(block, S, rounds, w) {
|
||||||
|
const mask = wordMask(w);
|
||||||
|
const lgw = BigInt(Math.floor(Math.log2(w)));
|
||||||
|
const lgMask = (1n << lgw) - 1n;
|
||||||
|
|
||||||
|
// Convert block to 4 words (A, B, C, D)
|
||||||
|
let [A, B, C, D] = bytesToWords(block, w);
|
||||||
|
|
||||||
|
// Pre-whitening
|
||||||
|
B = (B + S[0]) & mask;
|
||||||
|
D = (D + S[1]) & mask;
|
||||||
|
|
||||||
|
// Main rounds
|
||||||
|
for (let i = 1; i <= rounds; i++) {
|
||||||
|
// t = ROL(B * (2B + 1), lg(w))
|
||||||
|
const t = ROL((B * ((2n * B + 1n) & mask)) & mask, lgw, w, lgMask);
|
||||||
|
|
||||||
|
// u = ROL(D * (2D + 1), lg(w))
|
||||||
|
const u = ROL((D * ((2n * D + 1n) & mask)) & mask, lgw, w, lgMask);
|
||||||
|
|
||||||
|
// A = ROL(A ^ t, u) + S[2i]
|
||||||
|
A = (ROL(A ^ t, u, w, lgMask) + S[2 * i]) & mask;
|
||||||
|
|
||||||
|
// C = ROL(C ^ u, t) + S[2i + 1]
|
||||||
|
C = (ROL(C ^ u, t, w, lgMask) + S[2 * i + 1]) & mask;
|
||||||
|
|
||||||
|
// Rotate registers: (A, B, C, D) = (B, C, D, A)
|
||||||
|
const temp = A;
|
||||||
|
A = B;
|
||||||
|
B = C;
|
||||||
|
C = D;
|
||||||
|
D = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post-whitening
|
||||||
|
A = (A + S[2 * rounds + 2]) & mask;
|
||||||
|
C = (C + S[2 * rounds + 3]) & mask;
|
||||||
|
|
||||||
|
// Convert words back to bytes
|
||||||
|
return wordsToBytes([A, B, C, D], w);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt a single block using RC6
|
||||||
|
*
|
||||||
|
* @param {number[]} block - Ciphertext block (4*w/8 bytes)
|
||||||
|
* @param {bigint[]} S - Subkeys array
|
||||||
|
* @param {number} rounds - Number of rounds
|
||||||
|
* @param {number} w - Word size in bits
|
||||||
|
* @returns {number[]} - Plaintext block
|
||||||
|
*/
|
||||||
|
function decryptBlock(block, S, rounds, w) {
|
||||||
|
const mask = wordMask(w);
|
||||||
|
const lgw = BigInt(Math.floor(Math.log2(w)));
|
||||||
|
const lgMask = (1n << lgw) - 1n;
|
||||||
|
|
||||||
|
// Convert block to 4 words (A, B, C, D)
|
||||||
|
let [A, B, C, D] = bytesToWords(block, w);
|
||||||
|
|
||||||
|
// Reverse post-whitening
|
||||||
|
C = (C - S[2 * rounds + 3] + (1n << BigInt(w))) & mask;
|
||||||
|
A = (A - S[2 * rounds + 2] + (1n << BigInt(w))) & mask;
|
||||||
|
|
||||||
|
// Main rounds in reverse
|
||||||
|
for (let i = rounds; i >= 1; i--) {
|
||||||
|
// Reverse rotate registers: (A, B, C, D) = (D, A, B, C)
|
||||||
|
const temp = D;
|
||||||
|
D = C;
|
||||||
|
C = B;
|
||||||
|
B = A;
|
||||||
|
A = temp;
|
||||||
|
|
||||||
|
// u = ROL(D * (2D + 1), lg(w))
|
||||||
|
const u = ROL((D * ((2n * D + 1n) & mask)) & mask, lgw, w, lgMask);
|
||||||
|
|
||||||
|
// t = ROL(B * (2B + 1), lg(w))
|
||||||
|
const t = ROL((B * ((2n * B + 1n) & mask)) & mask, lgw, w, lgMask);
|
||||||
|
|
||||||
|
// C = ROR(C - S[2i + 1], t) ^ u
|
||||||
|
C = ROR((C - S[2 * i + 1] + (1n << BigInt(w))) & mask, t, w, lgMask) ^ u;
|
||||||
|
|
||||||
|
// A = ROR(A - S[2i], u) ^ t
|
||||||
|
A = ROR((A - S[2 * i] + (1n << BigInt(w))) & mask, u, w, lgMask) ^ t;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse pre-whitening
|
||||||
|
D = (D - S[1] + (1n << BigInt(w))) & mask;
|
||||||
|
B = (B - S[0] + (1n << BigInt(w))) & mask;
|
||||||
|
|
||||||
|
// Convert words back to bytes
|
||||||
|
return wordsToBytes([A, B, C, D], w);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XOR two blocks
|
||||||
|
* @param {number[]} a - First block
|
||||||
|
* @param {number[]} b - Second block
|
||||||
|
* @returns {number[]} - XOR result
|
||||||
|
*/
|
||||||
|
function xorBlocks(a, b) {
|
||||||
|
const result = new Array(a.length);
|
||||||
|
for (let i = 0; i < a.length; i++) {
|
||||||
|
result[i] = a[i] ^ b[i];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment counter (little-endian)
|
||||||
|
* @param {number[]} counter - Counter block
|
||||||
|
* @returns {number[]} - Incremented counter
|
||||||
|
*/
|
||||||
|
function incrementCounter(counter) {
|
||||||
|
const result = [...counter];
|
||||||
|
for (let i = 0; i < result.length; i++) {
|
||||||
|
result[i]++;
|
||||||
|
if (result[i] <= 255) break;
|
||||||
|
result[i] = 0;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply padding to message
|
||||||
|
* @param {number[]} message - Original message
|
||||||
|
* @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT")
|
||||||
|
* @param {number} blockSize - Block size in bytes
|
||||||
|
* @returns {number[]} - Padded message
|
||||||
|
*/
|
||||||
|
function applyPadding(message, padding, blockSize) {
|
||||||
|
const remainder = message.length % blockSize;
|
||||||
|
let nPadding = remainder === 0 ? 0 : blockSize - remainder;
|
||||||
|
|
||||||
|
// For PKCS5, always add at least one byte (full block if already aligned)
|
||||||
|
if (padding === "PKCS5" && remainder === 0) {
|
||||||
|
nPadding = blockSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nPadding === 0) return [...message];
|
||||||
|
|
||||||
|
const paddedMessage = [...message];
|
||||||
|
|
||||||
|
switch (padding) {
|
||||||
|
case "NO":
|
||||||
|
throw new OperationError(`No padding requested but input is not a ${blockSize}-byte multiple.`);
|
||||||
|
|
||||||
|
case "PKCS5":
|
||||||
|
for (let i = 0; i < nPadding; i++) {
|
||||||
|
paddedMessage.push(nPadding);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ZERO":
|
||||||
|
for (let i = 0; i < nPadding; i++) {
|
||||||
|
paddedMessage.push(0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "RANDOM":
|
||||||
|
for (let i = 0; i < nPadding; i++) {
|
||||||
|
paddedMessage.push(Math.floor(Math.random() * 256));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "BIT":
|
||||||
|
paddedMessage.push(0x80);
|
||||||
|
for (let i = 1; i < nPadding; i++) {
|
||||||
|
paddedMessage.push(0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new OperationError(`Unknown padding type: ${padding}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return paddedMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove padding from message
|
||||||
|
* @param {number[]} message - Padded message
|
||||||
|
* @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT")
|
||||||
|
* @param {number} blockSize - Block size in bytes
|
||||||
|
* @returns {number[]} - Unpadded message
|
||||||
|
*/
|
||||||
|
function removePadding(message, padding, blockSize) {
|
||||||
|
if (message.length === 0) return message;
|
||||||
|
|
||||||
|
switch (padding) {
|
||||||
|
case "NO":
|
||||||
|
case "ZERO":
|
||||||
|
case "RANDOM":
|
||||||
|
// These padding types cannot be reliably removed
|
||||||
|
return message;
|
||||||
|
|
||||||
|
case "PKCS5": {
|
||||||
|
const padByte = message[message.length - 1];
|
||||||
|
if (padByte > 0 && padByte <= blockSize) {
|
||||||
|
// Verify padding
|
||||||
|
for (let i = 0; i < padByte; i++) {
|
||||||
|
if (message[message.length - 1 - i] !== padByte) {
|
||||||
|
throw new OperationError("Invalid PKCS#5 padding.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return message.slice(0, message.length - padByte);
|
||||||
|
}
|
||||||
|
throw new OperationError("Invalid PKCS#5 padding.");
|
||||||
|
}
|
||||||
|
|
||||||
|
case "BIT": {
|
||||||
|
// Find 0x80 byte working backwards, skipping zeros
|
||||||
|
for (let i = message.length - 1; i >= 0; i--) {
|
||||||
|
if (message[i] === 0x80) {
|
||||||
|
return message.slice(0, i);
|
||||||
|
} else if (message[i] !== 0) {
|
||||||
|
throw new OperationError("Invalid BIT padding.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new OperationError("Invalid BIT padding.");
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new OperationError(`Unknown padding type: ${padding}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt using RC6 cipher with specified block mode
|
||||||
|
*
|
||||||
|
* @param {number[]} message - Plaintext as byte array
|
||||||
|
* @param {number[]} key - Key as byte array
|
||||||
|
* @param {number[]} iv - IV (block size bytes, not used for ECB)
|
||||||
|
* @param {string} mode - Block cipher mode ("ECB", "CBC", "CFB", "OFB", "CTR")
|
||||||
|
* @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT")
|
||||||
|
* @param {number} rounds - Number of rounds (default: 20)
|
||||||
|
* @param {number} w - Word size in bits (default: 32)
|
||||||
|
* @returns {number[]} - Ciphertext as byte array
|
||||||
|
*/
|
||||||
|
export function encryptRC6(message, key, iv, mode = "ECB", padding = "PKCS5", rounds = 20, w = 32) {
|
||||||
|
const blockSize = getBlockSize(w);
|
||||||
|
const messageLength = message.length;
|
||||||
|
if (messageLength === 0) return [];
|
||||||
|
|
||||||
|
const S = generateSubkeys(key, rounds, w);
|
||||||
|
|
||||||
|
// Apply padding for ECB/CBC modes
|
||||||
|
let paddedMessage;
|
||||||
|
if (mode === "ECB" || mode === "CBC") {
|
||||||
|
paddedMessage = applyPadding(message, padding, blockSize);
|
||||||
|
} else {
|
||||||
|
// Stream modes (CFB, OFB, CTR) don't need padding
|
||||||
|
paddedMessage = [...message];
|
||||||
|
}
|
||||||
|
|
||||||
|
const cipherText = [];
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case "ECB":
|
||||||
|
for (let i = 0; i < paddedMessage.length; i += blockSize) {
|
||||||
|
const block = paddedMessage.slice(i, i + blockSize);
|
||||||
|
cipherText.push(...encryptBlock(block, S, rounds, w));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "CBC": {
|
||||||
|
let ivBlock = [...iv];
|
||||||
|
for (let i = 0; i < paddedMessage.length; i += blockSize) {
|
||||||
|
const block = paddedMessage.slice(i, i + blockSize);
|
||||||
|
const xored = xorBlocks(block, ivBlock);
|
||||||
|
ivBlock = encryptBlock(xored, S, rounds, w);
|
||||||
|
cipherText.push(...ivBlock);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "CFB": {
|
||||||
|
let ivBlock = [...iv];
|
||||||
|
for (let i = 0; i < paddedMessage.length; i += blockSize) {
|
||||||
|
const encrypted = encryptBlock(ivBlock, S, rounds, w);
|
||||||
|
const block = paddedMessage.slice(i, i + blockSize);
|
||||||
|
// Pad block if shorter than blockSize
|
||||||
|
while (block.length < blockSize) block.push(0);
|
||||||
|
ivBlock = xorBlocks(encrypted, block);
|
||||||
|
cipherText.push(...ivBlock);
|
||||||
|
}
|
||||||
|
return cipherText.slice(0, messageLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
case "OFB": {
|
||||||
|
let ivBlock = [...iv];
|
||||||
|
for (let i = 0; i < paddedMessage.length; i += blockSize) {
|
||||||
|
ivBlock = encryptBlock(ivBlock, S, rounds, w);
|
||||||
|
const block = paddedMessage.slice(i, i + blockSize);
|
||||||
|
// Pad block if shorter than blockSize
|
||||||
|
while (block.length < blockSize) block.push(0);
|
||||||
|
cipherText.push(...xorBlocks(ivBlock, block));
|
||||||
|
}
|
||||||
|
return cipherText.slice(0, messageLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
case "CTR": {
|
||||||
|
let counter = [...iv];
|
||||||
|
for (let i = 0; i < paddedMessage.length; i += blockSize) {
|
||||||
|
const encrypted = encryptBlock(counter, S, rounds, w);
|
||||||
|
const block = paddedMessage.slice(i, i + blockSize);
|
||||||
|
// Pad block if shorter than blockSize
|
||||||
|
while (block.length < blockSize) block.push(0);
|
||||||
|
cipherText.push(...xorBlocks(encrypted, block));
|
||||||
|
counter = incrementCounter(counter);
|
||||||
|
}
|
||||||
|
return cipherText.slice(0, messageLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new OperationError(`Invalid block cipher mode: ${mode}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cipherText;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt using RC6 cipher with specified block mode
|
||||||
|
*
|
||||||
|
* @param {number[]} cipherText - Ciphertext as byte array
|
||||||
|
* @param {number[]} key - Key as byte array
|
||||||
|
* @param {number[]} iv - IV (block size bytes, not used for ECB)
|
||||||
|
* @param {string} mode - Block cipher mode ("ECB", "CBC", "CFB", "OFB", "CTR")
|
||||||
|
* @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT")
|
||||||
|
* @param {number} rounds - Number of rounds (default: 20)
|
||||||
|
* @param {number} w - Word size in bits (default: 32)
|
||||||
|
* @returns {number[]} - Plaintext as byte array
|
||||||
|
*/
|
||||||
|
export function decryptRC6(cipherText, key, iv, mode = "ECB", padding = "PKCS5", rounds = 20, w = 32) {
|
||||||
|
const blockSize = getBlockSize(w);
|
||||||
|
const originalLength = cipherText.length;
|
||||||
|
if (originalLength === 0) return [];
|
||||||
|
|
||||||
|
const S = generateSubkeys(key, rounds, w);
|
||||||
|
|
||||||
|
if (mode === "ECB" || mode === "CBC") {
|
||||||
|
if ((originalLength % blockSize) !== 0)
|
||||||
|
throw new OperationError(`Invalid ciphertext length: ${originalLength} bytes. Must be a multiple of ${blockSize}.`);
|
||||||
|
} else {
|
||||||
|
// Pad for stream modes
|
||||||
|
while ((cipherText.length % blockSize) !== 0)
|
||||||
|
cipherText.push(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const plainText = [];
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case "ECB":
|
||||||
|
for (let i = 0; i < cipherText.length; i += blockSize) {
|
||||||
|
const block = cipherText.slice(i, i + blockSize);
|
||||||
|
plainText.push(...decryptBlock(block, S, rounds, w));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "CBC": {
|
||||||
|
let ivBlock = [...iv];
|
||||||
|
for (let i = 0; i < cipherText.length; i += blockSize) {
|
||||||
|
const block = cipherText.slice(i, i + blockSize);
|
||||||
|
const decrypted = decryptBlock(block, S, rounds, w);
|
||||||
|
plainText.push(...xorBlocks(decrypted, ivBlock));
|
||||||
|
ivBlock = block;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "CFB": {
|
||||||
|
let ivBlock = [...iv];
|
||||||
|
for (let i = 0; i < cipherText.length; i += blockSize) {
|
||||||
|
const encrypted = encryptBlock(ivBlock, S, rounds, w);
|
||||||
|
const block = cipherText.slice(i, i + blockSize);
|
||||||
|
plainText.push(...xorBlocks(encrypted, block));
|
||||||
|
ivBlock = block;
|
||||||
|
}
|
||||||
|
return plainText.slice(0, originalLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
case "OFB": {
|
||||||
|
let ivBlock = [...iv];
|
||||||
|
for (let i = 0; i < cipherText.length; i += blockSize) {
|
||||||
|
ivBlock = encryptBlock(ivBlock, S, rounds, w);
|
||||||
|
const block = cipherText.slice(i, i + blockSize);
|
||||||
|
plainText.push(...xorBlocks(ivBlock, block));
|
||||||
|
}
|
||||||
|
return plainText.slice(0, originalLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
case "CTR": {
|
||||||
|
let counter = [...iv];
|
||||||
|
for (let i = 0; i < cipherText.length; i += blockSize) {
|
||||||
|
const encrypted = encryptBlock(counter, S, rounds, w);
|
||||||
|
const block = cipherText.slice(i, i + blockSize);
|
||||||
|
plainText.push(...xorBlocks(encrypted, block));
|
||||||
|
counter = incrementCounter(counter);
|
||||||
|
}
|
||||||
|
return plainText.slice(0, originalLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new OperationError(`Invalid block cipher mode: ${mode}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove padding for ECB/CBC modes
|
||||||
|
if (mode === "ECB" || mode === "CBC") {
|
||||||
|
return removePadding(plainText, padding, blockSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return plainText.slice(0, originalLength);
|
||||||
|
}
|
||||||
@ -76,7 +76,7 @@ class A1Z26CipherDecode extends Operation {
|
|||||||
const delim = Utils.charRep(args[0] || "Space");
|
const delim = Utils.charRep(args[0] || "Space");
|
||||||
|
|
||||||
if (input.length === 0) {
|
if (input.length === 0) {
|
||||||
return [];
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const bites = input.split(delim);
|
const bites = input.split(delim);
|
||||||
|
|||||||
@ -9,13 +9,19 @@ import OperationError from "../errors/OperationError.mjs";
|
|||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import Jimp from "jimp/es/index.js";
|
import {
|
||||||
|
Jimp,
|
||||||
|
JimpMime,
|
||||||
|
ResizeStrategy,
|
||||||
|
measureText,
|
||||||
|
measureTextHeight,
|
||||||
|
loadFont,
|
||||||
|
} from "jimp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add Text To Image operation
|
* Add Text To Image operation
|
||||||
*/
|
*/
|
||||||
class AddTextToImage extends Operation {
|
class AddTextToImage extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AddTextToImage constructor
|
* AddTextToImage constructor
|
||||||
*/
|
*/
|
||||||
@ -24,7 +30,8 @@ class AddTextToImage extends Operation {
|
|||||||
|
|
||||||
this.name = "Add Text To Image";
|
this.name = "Add Text To Image";
|
||||||
this.module = "Image";
|
this.module = "Image";
|
||||||
this.description = "Adds text onto an image.<br><br>Text can be horizontally or vertically aligned, or the position can be manually specified.<br>Variants of the Roboto font face are available in any size or colour.";
|
this.description =
|
||||||
|
"Adds text onto an image.<br><br>Text can be horizontally or vertically aligned, or the position can be manually specified.<br>Variants of the Roboto font face are available in any size or colour.";
|
||||||
this.infoURL = "";
|
this.infoURL = "";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "ArrayBuffer";
|
this.outputType = "ArrayBuffer";
|
||||||
@ -33,72 +40,67 @@ class AddTextToImage extends Operation {
|
|||||||
{
|
{
|
||||||
name: "Text",
|
name: "Text",
|
||||||
type: "string",
|
type: "string",
|
||||||
value: ""
|
value: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Horizontal align",
|
name: "Horizontal align",
|
||||||
type: "option",
|
type: "option",
|
||||||
value: ["None", "Left", "Center", "Right"]
|
value: ["None", "Left", "Center", "Right"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Vertical align",
|
name: "Vertical align",
|
||||||
type: "option",
|
type: "option",
|
||||||
value: ["None", "Top", "Middle", "Bottom"]
|
value: ["None", "Top", "Middle", "Bottom"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "X position",
|
name: "X position",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 0
|
value: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Y position",
|
name: "Y position",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 0
|
value: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Size",
|
name: "Size",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 32,
|
value: 32,
|
||||||
min: 8
|
min: 8,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Font face",
|
name: "Font face",
|
||||||
type: "option",
|
type: "option",
|
||||||
value: [
|
value: ["Roboto", "Roboto Black", "Roboto Mono", "Roboto Slab"],
|
||||||
"Roboto",
|
|
||||||
"Roboto Black",
|
|
||||||
"Roboto Mono",
|
|
||||||
"Roboto Slab"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Red",
|
name: "Red",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 255,
|
value: 255,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 255
|
max: 255,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Green",
|
name: "Green",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 255,
|
value: 255,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 255
|
max: 255,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Blue",
|
name: "Blue",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 255,
|
value: 255,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 255
|
max: 255,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Alpha",
|
name: "Alpha",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 255,
|
value: 255,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 255
|
max: 255,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,41 +133,60 @@ class AddTextToImage extends Operation {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error loading image. (${err})`);
|
throw new OperationError(`Error loading image. (${err})`);
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
if (isWorkerEnvironment())
|
|
||||||
self.sendStatusMessage("Adding text to image...");
|
|
||||||
|
|
||||||
const fontsMap = {};
|
if (isWorkerEnvironment())
|
||||||
|
self.sendStatusMessage("Adding text to image...");
|
||||||
|
|
||||||
|
const fontsMap = {};
|
||||||
|
try {
|
||||||
const fonts = [
|
const fonts = [
|
||||||
import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/Roboto72White.fnt"),
|
import(
|
||||||
import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoBlack72White.fnt"),
|
/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/Roboto72White.fnt"
|
||||||
import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoMono72White.fnt"),
|
),
|
||||||
import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoSlab72White.fnt")
|
import(
|
||||||
|
/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoBlack72White.fnt"
|
||||||
|
),
|
||||||
|
import(
|
||||||
|
/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoMono72White.fnt"
|
||||||
|
),
|
||||||
|
import(
|
||||||
|
/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoSlab72White.fnt"
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
await Promise.all(fonts)
|
await Promise.all(fonts).then((fonts) => {
|
||||||
.then(fonts => {
|
fontsMap.Roboto = fonts[0];
|
||||||
fontsMap.Roboto = fonts[0];
|
fontsMap["Roboto Black"] = fonts[1];
|
||||||
fontsMap["Roboto Black"] = fonts[1];
|
fontsMap["Roboto Mono"] = fonts[2];
|
||||||
fontsMap["Roboto Mono"] = fonts[2];
|
fontsMap["Roboto Slab"] = fonts[3];
|
||||||
fontsMap["Roboto Slab"] = fonts[3];
|
});
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// Make Webpack load the png font images
|
// Make Webpack load the png font images
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/Roboto72White.png"),
|
import(
|
||||||
import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoSlab72White.png"),
|
/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/Roboto72White.png"
|
||||||
import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoMono72White.png"),
|
),
|
||||||
import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoBlack72White.png")
|
import(
|
||||||
|
/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoSlab72White.png"
|
||||||
|
),
|
||||||
|
import(
|
||||||
|
/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoMono72White.png"
|
||||||
|
),
|
||||||
|
import(
|
||||||
|
/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoBlack72White.png"
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
|
} catch (err) {
|
||||||
|
throw new OperationError(`Error preparing fonts. (${err})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let jimpFont;
|
||||||
|
try {
|
||||||
const font = fontsMap[fontFace];
|
const font = fontsMap[fontFace];
|
||||||
|
|
||||||
// LoadFont needs an absolute url, so append the font name to self.docURL
|
// LoadFont needs an absolute url, so append the font name to self.docURL
|
||||||
const jimpFont = await Jimp.loadFont(self.docURL + "/" + font.default);
|
jimpFont = await loadFont(self.docURL + "/" + font.default);
|
||||||
|
|
||||||
jimpFont.pages.forEach(function(page) {
|
jimpFont.pages.forEach(function (page) {
|
||||||
if (page.bitmap) {
|
if (page.bitmap) {
|
||||||
// Adjust the RGB values of the image pages to change the font colour.
|
// Adjust the RGB values of the image pages to change the font colour.
|
||||||
const pageWidth = page.bitmap.width;
|
const pageWidth = page.bitmap.width;
|
||||||
@ -175,32 +196,56 @@ class AddTextToImage extends Operation {
|
|||||||
const idx = (iy * pageWidth + ix) << 2;
|
const idx = (iy * pageWidth + ix) << 2;
|
||||||
|
|
||||||
const newRed = page.bitmap.data[idx] - (255 - red);
|
const newRed = page.bitmap.data[idx] - (255 - red);
|
||||||
const newGreen = page.bitmap.data[idx + 1] - (255 - green);
|
const newGreen =
|
||||||
const newBlue = page.bitmap.data[idx + 2] - (255 - blue);
|
page.bitmap.data[idx + 1] - (255 - green);
|
||||||
const newAlpha = page.bitmap.data[idx + 3] - (255 - alpha);
|
const newBlue =
|
||||||
|
page.bitmap.data[idx + 2] - (255 - blue);
|
||||||
|
const newAlpha =
|
||||||
|
page.bitmap.data[idx + 3] - (255 - alpha);
|
||||||
|
|
||||||
// Make sure the bitmap values don't go below 0 as that makes jimp very unhappy
|
// Make sure the bitmap values don't go below 0 as that makes jimp very unhappy
|
||||||
page.bitmap.data[idx] = (newRed > 0) ? newRed : 0;
|
page.bitmap.data[idx] = newRed > 0 ? newRed : 0;
|
||||||
page.bitmap.data[idx + 1] = (newGreen > 0) ? newGreen : 0;
|
page.bitmap.data[idx + 1] =
|
||||||
page.bitmap.data[idx + 2] = (newBlue > 0) ? newBlue : 0;
|
newGreen > 0 ? newGreen : 0;
|
||||||
page.bitmap.data[idx + 3] = (newAlpha > 0) ? newAlpha : 0;
|
page.bitmap.data[idx + 2] =
|
||||||
|
newBlue > 0 ? newBlue : 0;
|
||||||
|
page.bitmap.data[idx + 3] =
|
||||||
|
newAlpha > 0 ? newAlpha : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} catch (err) {
|
||||||
|
throw new OperationError(`Error loading font. (${err})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
// Create a temporary image to hold the rendered text
|
// Create a temporary image to hold the rendered text
|
||||||
const textImage = new Jimp(Jimp.measureText(jimpFont, text), Jimp.measureTextHeight(jimpFont, text));
|
const textImage = new Jimp({
|
||||||
textImage.print(jimpFont, 0, 0, text);
|
width: measureText(jimpFont, text),
|
||||||
|
height: measureTextHeight(jimpFont, text),
|
||||||
|
});
|
||||||
|
textImage.print({
|
||||||
|
font: jimpFont,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
text,
|
||||||
|
});
|
||||||
|
|
||||||
// Scale the rendered text image to the correct size
|
// Scale the rendered text image to the correct size
|
||||||
const scaleFactor = size / 72;
|
const scaleFactor = size / 72;
|
||||||
if (size !== 1) {
|
if (size !== 1) {
|
||||||
// Use bicubic for decreasing size
|
// Use bicubic for decreasing size
|
||||||
if (size > 1) {
|
if (size > 1) {
|
||||||
textImage.scale(scaleFactor, Jimp.RESIZE_BICUBIC);
|
textImage.scale({
|
||||||
|
f: scaleFactor,
|
||||||
|
mode: ResizeStrategy.BICUBIC,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
textImage.scale(scaleFactor, Jimp.RESIZE_BILINEAR);
|
textImage.scale({
|
||||||
|
f: scaleFactor,
|
||||||
|
mode: ResizeStrategy.BILINEAR,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,10 +255,10 @@ class AddTextToImage extends Operation {
|
|||||||
xPos = 0;
|
xPos = 0;
|
||||||
break;
|
break;
|
||||||
case "Center":
|
case "Center":
|
||||||
xPos = (image.getWidth() / 2) - (textImage.getWidth() / 2);
|
xPos = image.width / 2 - textImage.width / 2;
|
||||||
break;
|
break;
|
||||||
case "Right":
|
case "Right":
|
||||||
xPos = image.getWidth() - textImage.getWidth();
|
xPos = image.width - textImage.width;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,25 +267,33 @@ class AddTextToImage extends Operation {
|
|||||||
yPos = 0;
|
yPos = 0;
|
||||||
break;
|
break;
|
||||||
case "Middle":
|
case "Middle":
|
||||||
yPos = (image.getHeight() / 2) - (textImage.getHeight() / 2);
|
yPos = image.height / 2 - textImage.height / 2;
|
||||||
break;
|
break;
|
||||||
case "Bottom":
|
case "Bottom":
|
||||||
yPos = image.getHeight() - textImage.getHeight();
|
yPos = image.height - textImage.height;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blit the rendered text image onto the original source image
|
// Blit the rendered text image onto the original source image
|
||||||
image.blit(textImage, xPos, yPos);
|
image.blit({
|
||||||
|
src: textImage,
|
||||||
|
x: xPos,
|
||||||
|
y: yPos,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
throw new OperationError(`Error adding text to image. (${err})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.mime === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
imageBuffer = await image.getBuffer(JimpMime.png);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
imageBuffer = await image.getBuffer(image.mime);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error adding text to image. (${err})`);
|
throw new OperationError(`Error exporting image. (${err})`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,7 +314,6 @@ class AddTextToImage extends Operation {
|
|||||||
|
|
||||||
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AddTextToImage;
|
export default AddTextToImage;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @author n1474335 [n1474335@gmail.com]
|
* @author n1474335 [n1474335@gmail.com]
|
||||||
|
* @author ko80240 [csk.dev@proton.me]
|
||||||
* @copyright Crown Copyright 2023
|
* @copyright Crown Copyright 2023
|
||||||
* @license Apache-2.0
|
* @license Apache-2.0
|
||||||
*/
|
*/
|
||||||
@ -8,6 +9,7 @@ import * as uuid from "uuid";
|
|||||||
|
|
||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.mjs";
|
||||||
import OperationError from "../errors/OperationError.mjs";
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import { toHex } from "../lib/Hex.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Analyse UUID operation
|
* Analyse UUID operation
|
||||||
@ -22,27 +24,128 @@ class AnalyseUUID extends Operation {
|
|||||||
|
|
||||||
this.name = "Analyse UUID";
|
this.name = "Analyse UUID";
|
||||||
this.module = "Crypto";
|
this.module = "Crypto";
|
||||||
this.description = "Tries to determine information about a given UUID and suggests which version may have been used to generate it";
|
this.description = "Operation for extracting metadata and detecting the version of a given UUID.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Universally_unique_identifier";
|
this.infoURL = "https://wikipedia.org/wiki/Universally_unique_identifier";
|
||||||
this.inputType = "string";
|
this.inputType = "string";
|
||||||
this.outputType = "string";
|
this.outputType = "string";
|
||||||
this.args = [];
|
this.args = [
|
||||||
|
{
|
||||||
|
name: "Include Metadata",
|
||||||
|
type: "boolean",
|
||||||
|
value: true
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} input
|
* @param {string} input - Expects a valid UUID string
|
||||||
* @param {Object[]} args
|
* @param {Object[]} args
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
|
input = input.trim();
|
||||||
|
|
||||||
|
let uuidVersion, uuidBytes;
|
||||||
try {
|
try {
|
||||||
const uuidVersion = uuid.version(input);
|
uuidVersion = uuid.version(input); // Re-using the uuid library to extract version
|
||||||
return "UUID version: " + uuidVersion;
|
uuidBytes = uuid.parse(input); // Re-using the uuid library to parse bytes
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new OperationError("Invalid UUID");
|
throw new OperationError("Invalid UUID");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
const [includeMetadata] = args;
|
||||||
|
const dv = new DataView(uuidBytes.buffer, uuidBytes.byteOffset, uuidBytes.byteLength); // Dataview helps handle the multi-byte ints
|
||||||
|
const uuidInteger = (dv.getBigUint64(0) << 64n) | dv.getBigUint64(8);
|
||||||
|
|
||||||
|
const sections = [`Version:\n${uuidVersion}`];
|
||||||
|
|
||||||
|
if (includeMetadata) {
|
||||||
|
const parser = UUID_PARSERS[uuidVersion];
|
||||||
|
const decoded = parser?.(uuidBytes, dv);
|
||||||
|
sections.push(formatDecoded(decoded));
|
||||||
|
}
|
||||||
|
|
||||||
|
sections.push(`UUID Integer:\n${uuidInteger}`);
|
||||||
|
|
||||||
|
return sections.filter(Boolean).join("\n\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AnalyseUUID;
|
export default AnalyseUUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata can be extracted for versions 1, 6, and 7.
|
||||||
|
* Enum-like frozen mapping of UUID version to parser function.
|
||||||
|
*/
|
||||||
|
const UUID_PARSERS = Object.freeze({
|
||||||
|
1: parsev1v6,
|
||||||
|
6: parsev1v6,
|
||||||
|
7: parsev7,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Versions 1 and 6. Note 6 is a re-order of 1.
|
||||||
|
* Version 1 == layout: timeLow(32) | timeMid(16) | timeHi(12)
|
||||||
|
* Version 6 == layout: timeHi(32) | timeMid(16) | timeLow(12)
|
||||||
|
*/
|
||||||
|
function parsev1v6(uuidBytes, dv) {
|
||||||
|
const isV1 = (uuidBytes[6] >> 4) === 1;
|
||||||
|
|
||||||
|
const timeStamp =
|
||||||
|
isV1 ? (
|
||||||
|
(BigInt(dv.getUint16(6) & 0x0fff) << 48n) | // mask off version bits
|
||||||
|
(BigInt(dv.getUint16(4)) << 32n) |
|
||||||
|
BigInt(dv.getUint32(0))
|
||||||
|
) : (
|
||||||
|
(BigInt(dv.getUint32(0)) << 28n) |
|
||||||
|
(BigInt(dv.getUint16(4)) << 12n) |
|
||||||
|
(BigInt(dv.getUint16(6) & 0x0fff))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Convert to Unix time
|
||||||
|
const milliseconds =
|
||||||
|
Number(
|
||||||
|
(timeStamp - 122192928000000000n) / 10000n
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
timestamp: milliseconds,
|
||||||
|
isoTimestamp: new Date(milliseconds).toISOString(),
|
||||||
|
clock: ((uuidBytes[8] & 0x3f) << 8) | uuidBytes[9],
|
||||||
|
node: toHex(uuidBytes.slice(10), ":").toUpperCase()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Version 7 */
|
||||||
|
function parsev7(uuidBytes, dv) {
|
||||||
|
const milliseconds = Number((BigInt(dv.getUint32(0)) << 16n) | BigInt(dv.getUint16(4)));
|
||||||
|
|
||||||
|
return {
|
||||||
|
timestamp: milliseconds,
|
||||||
|
isoTimestamp: new Date(milliseconds).toISOString(),
|
||||||
|
randA: ((uuidBytes[6] & 0x0f) << 8) | uuidBytes[7],
|
||||||
|
randB: toHex(uuidBytes.slice(8), "").toUpperCase()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats metadata
|
||||||
|
*
|
||||||
|
* @param {Object|undefined} decoded
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function formatDecoded(decoded) {
|
||||||
|
if (!decoded) return "No metadata available. Only versions 1, 6, 7 are supported.";
|
||||||
|
|
||||||
|
return Object.entries({
|
||||||
|
"Timestamp": decoded.timestamp,
|
||||||
|
"Timestamp (ISO)": decoded.isoTimestamp,
|
||||||
|
"Node": decoded.node,
|
||||||
|
"Clock": decoded.clock,
|
||||||
|
"Rand A": decoded.randA,
|
||||||
|
"Rand B": decoded.randB
|
||||||
|
})
|
||||||
|
.filter(([, value]) => value !== undefined)
|
||||||
|
.map(([label, value]) => `${label}:\n${value}`)
|
||||||
|
.join("\n\n");
|
||||||
|
}
|
||||||
|
|||||||
@ -9,14 +9,12 @@ import OperationError from "../errors/OperationError.mjs";
|
|||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { gaussianBlur } from "../lib/ImageManipulation.mjs";
|
import { Jimp, JimpMime } from "jimp";
|
||||||
import Jimp from "jimp/es/index.js";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blur Image operation
|
* Blur Image operation
|
||||||
*/
|
*/
|
||||||
class BlurImage extends Operation {
|
class BlurImage extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BlurImage constructor
|
* BlurImage constructor
|
||||||
*/
|
*/
|
||||||
@ -25,7 +23,8 @@ class BlurImage extends Operation {
|
|||||||
|
|
||||||
this.name = "Blur Image";
|
this.name = "Blur Image";
|
||||||
this.module = "Image";
|
this.module = "Image";
|
||||||
this.description = "Applies a blur effect to the image.<br><br>Gaussian blur is much slower than fast blur, but produces better results.";
|
this.description =
|
||||||
|
"Applies a blur effect to the image.<br><br>Gaussian blur is much slower than fast blur, but produces better results.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Gaussian_blur";
|
this.infoURL = "https://wikipedia.org/wiki/Gaussian_blur";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "ArrayBuffer";
|
this.outputType = "ArrayBuffer";
|
||||||
@ -35,13 +34,13 @@ class BlurImage extends Operation {
|
|||||||
name: "Amount",
|
name: "Amount",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 5,
|
value: 5,
|
||||||
min: 1
|
min: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Type",
|
name: "Type",
|
||||||
type: "option",
|
type: "option",
|
||||||
value: ["Fast", "Gaussian"]
|
value: ["Fast", "Gaussian"],
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,15 +72,15 @@ class BlurImage extends Operation {
|
|||||||
case "Gaussian":
|
case "Gaussian":
|
||||||
if (isWorkerEnvironment())
|
if (isWorkerEnvironment())
|
||||||
self.sendStatusMessage("Gaussian blurring image...");
|
self.sendStatusMessage("Gaussian blurring image...");
|
||||||
image = gaussianBlur(image, blurAmount);
|
image.gaussian(blurAmount);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.mime === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
imageBuffer = await image.getBuffer(JimpMime.png);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
imageBuffer = await image.getBuffer(image.mime);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -106,7 +105,6 @@ class BlurImage extends Operation {
|
|||||||
|
|
||||||
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BlurImage;
|
export default BlurImage;
|
||||||
|
|||||||
@ -9,13 +9,18 @@ import OperationError from "../errors/OperationError.mjs";
|
|||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import Jimp from "jimp/es/index.js";
|
import {
|
||||||
|
Jimp,
|
||||||
|
JimpMime,
|
||||||
|
ResizeStrategy,
|
||||||
|
HorizontalAlign,
|
||||||
|
VerticalAlign,
|
||||||
|
} from "jimp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contain Image operation
|
* Contain Image operation
|
||||||
*/
|
*/
|
||||||
class ContainImage extends Operation {
|
class ContainImage extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ContainImage constructor
|
* ContainImage constructor
|
||||||
*/
|
*/
|
||||||
@ -24,7 +29,8 @@ class ContainImage extends Operation {
|
|||||||
|
|
||||||
this.name = "Contain Image";
|
this.name = "Contain Image";
|
||||||
this.module = "Image";
|
this.module = "Image";
|
||||||
this.description = "Scales an image to the specified width and height, maintaining the aspect ratio. The image may be letterboxed.";
|
this.description =
|
||||||
|
"Scales an image to the specified width and height, maintaining the aspect ratio. The image may be letterboxed.";
|
||||||
this.infoURL = "";
|
this.infoURL = "";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "ArrayBuffer";
|
this.outputType = "ArrayBuffer";
|
||||||
@ -34,33 +40,25 @@ class ContainImage extends Operation {
|
|||||||
name: "Width",
|
name: "Width",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 100,
|
value: 100,
|
||||||
min: 1
|
min: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Height",
|
name: "Height",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 100,
|
value: 100,
|
||||||
min: 1
|
min: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Horizontal align",
|
name: "Horizontal align",
|
||||||
type: "option",
|
type: "option",
|
||||||
value: [
|
value: ["Left", "Center", "Right"],
|
||||||
"Left",
|
defaultIndex: 1,
|
||||||
"Center",
|
|
||||||
"Right"
|
|
||||||
],
|
|
||||||
defaultIndex: 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Vertical align",
|
name: "Vertical align",
|
||||||
type: "option",
|
type: "option",
|
||||||
value: [
|
value: ["Top", "Middle", "Bottom"],
|
||||||
"Top",
|
defaultIndex: 1,
|
||||||
"Middle",
|
|
||||||
"Bottom"
|
|
||||||
],
|
|
||||||
defaultIndex: 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Resizing algorithm",
|
name: "Resizing algorithm",
|
||||||
@ -70,15 +68,15 @@ class ContainImage extends Operation {
|
|||||||
"Bilinear",
|
"Bilinear",
|
||||||
"Bicubic",
|
"Bicubic",
|
||||||
"Hermite",
|
"Hermite",
|
||||||
"Bezier"
|
"Bezier",
|
||||||
],
|
],
|
||||||
defaultIndex: 1
|
defaultIndex: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Opaque background",
|
name: "Opaque background",
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
value: true
|
value: true,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,20 +89,20 @@ class ContainImage extends Operation {
|
|||||||
const [width, height, hAlign, vAlign, alg, opaqueBg] = args;
|
const [width, height, hAlign, vAlign, alg, opaqueBg] = args;
|
||||||
|
|
||||||
const resizeMap = {
|
const resizeMap = {
|
||||||
"Nearest Neighbour": Jimp.RESIZE_NEAREST_NEIGHBOR,
|
"Nearest Neighbour": ResizeStrategy.NEAREST_NEIGHBOR,
|
||||||
"Bilinear": Jimp.RESIZE_BILINEAR,
|
Bilinear: ResizeStrategy.BILINEAR,
|
||||||
"Bicubic": Jimp.RESIZE_BICUBIC,
|
Bicubic: ResizeStrategy.BICUBIC,
|
||||||
"Hermite": Jimp.RESIZE_HERMITE,
|
Hermite: ResizeStrategy.HERMITE,
|
||||||
"Bezier": Jimp.RESIZE_BEZIER
|
Bezier: ResizeStrategy.BEZIER,
|
||||||
};
|
};
|
||||||
|
|
||||||
const alignMap = {
|
const alignMap = {
|
||||||
"Left": Jimp.HORIZONTAL_ALIGN_LEFT,
|
Left: HorizontalAlign.LEFT,
|
||||||
"Center": Jimp.HORIZONTAL_ALIGN_CENTER,
|
Center: HorizontalAlign.CENTER,
|
||||||
"Right": Jimp.HORIZONTAL_ALIGN_RIGHT,
|
Right: HorizontalAlign.RIGHT,
|
||||||
"Top": Jimp.VERTICAL_ALIGN_TOP,
|
Top: VerticalAlign.TOP,
|
||||||
"Middle": Jimp.VERTICAL_ALIGN_MIDDLE,
|
Middle: VerticalAlign.MIDDLE,
|
||||||
"Bottom": Jimp.VERTICAL_ALIGN_BOTTOM
|
Bottom: VerticalAlign.BOTTOM,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isImage(input)) {
|
if (!isImage(input)) {
|
||||||
@ -117,22 +115,35 @@ class ContainImage extends Operation {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error loading image. (${err})`);
|
throw new OperationError(`Error loading image. (${err})`);
|
||||||
}
|
}
|
||||||
|
const originalMime = image.mime;
|
||||||
try {
|
try {
|
||||||
if (isWorkerEnvironment())
|
if (isWorkerEnvironment())
|
||||||
self.sendStatusMessage("Containing image...");
|
self.sendStatusMessage("Containing image...");
|
||||||
image.contain(width, height, alignMap[hAlign] | alignMap[vAlign], resizeMap[alg]);
|
image.contain({
|
||||||
|
w: width,
|
||||||
|
h: height,
|
||||||
|
align: alignMap[hAlign] | alignMap[vAlign],
|
||||||
|
mode: resizeMap[alg],
|
||||||
|
});
|
||||||
|
|
||||||
if (opaqueBg) {
|
if (opaqueBg) {
|
||||||
const newImage = await Jimp.read(width, height, 0x000000FF);
|
const newImage = new Jimp({
|
||||||
newImage.blit(image, 0, 0);
|
width,
|
||||||
image = newImage;
|
height,
|
||||||
|
color: 0x000000ff,
|
||||||
|
});
|
||||||
|
image = newImage.blit({
|
||||||
|
src: image,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (originalMime === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
imageBuffer = await image.getBuffer(JimpMime.png);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
imageBuffer = await image.getBuffer(originalMime);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -156,7 +167,6 @@ class ContainImage extends Operation {
|
|||||||
|
|
||||||
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ContainImage;
|
export default ContainImage;
|
||||||
|
|||||||
@ -8,13 +8,12 @@ import Operation from "../Operation.mjs";
|
|||||||
import OperationError from "../errors/OperationError.mjs";
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import Jimp from "jimp/es/index.js";
|
import { Jimp, JimpMime, PNGFilterType } from "jimp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert Image Format operation
|
* Convert Image Format operation
|
||||||
*/
|
*/
|
||||||
class ConvertImageFormat extends Operation {
|
class ConvertImageFormat extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ConvertImageFormat constructor
|
* ConvertImageFormat constructor
|
||||||
*/
|
*/
|
||||||
@ -23,7 +22,8 @@ class ConvertImageFormat extends Operation {
|
|||||||
|
|
||||||
this.name = "Convert Image Format";
|
this.name = "Convert Image Format";
|
||||||
this.module = "Image";
|
this.module = "Image";
|
||||||
this.description = "Converts an image between different formats. Supported formats:<br><ul><li>Joint Photographic Experts Group (JPEG)</li><li>Portable Network Graphics (PNG)</li><li>Bitmap (BMP)</li><li>Tagged Image File Format (TIFF)</li></ul><br>Note: GIF files are supported for input, but cannot be outputted.";
|
this.description =
|
||||||
|
"Converts an image between different formats. Supported formats:<br><ul><li>Joint Photographic Experts Group (JPEG)</li><li>Portable Network Graphics (PNG)</li><li>Bitmap (BMP)</li><li>Tagged Image File Format (TIFF)</li></ul><br>Note: GIF files are supported for input, but cannot be outputted.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Image_file_formats";
|
this.infoURL = "https://wikipedia.org/wiki/Image_file_formats";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "ArrayBuffer";
|
this.outputType = "ArrayBuffer";
|
||||||
@ -32,39 +32,27 @@ class ConvertImageFormat extends Operation {
|
|||||||
{
|
{
|
||||||
name: "Output Format",
|
name: "Output Format",
|
||||||
type: "option",
|
type: "option",
|
||||||
value: [
|
value: ["JPEG", "PNG", "BMP", "TIFF"],
|
||||||
"JPEG",
|
|
||||||
"PNG",
|
|
||||||
"BMP",
|
|
||||||
"TIFF"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "JPEG Quality",
|
name: "JPEG Quality",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 80,
|
value: 80,
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 100
|
max: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "PNG Filter Type",
|
name: "PNG Filter Type",
|
||||||
type: "option",
|
type: "option",
|
||||||
value: [
|
value: ["Auto", "None", "Sub", "Up", "Average", "Paeth"],
|
||||||
"Auto",
|
|
||||||
"None",
|
|
||||||
"Sub",
|
|
||||||
"Up",
|
|
||||||
"Average",
|
|
||||||
"Paeth"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "PNG Deflate Level",
|
name: "PNG Deflate Level",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 9,
|
value: 9,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 9
|
max: 9,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,19 +64,19 @@ class ConvertImageFormat extends Operation {
|
|||||||
async run(input, args) {
|
async run(input, args) {
|
||||||
const [format, jpegQuality, pngFilterType, pngDeflateLevel] = args;
|
const [format, jpegQuality, pngFilterType, pngDeflateLevel] = args;
|
||||||
const formatMap = {
|
const formatMap = {
|
||||||
"JPEG": Jimp.MIME_JPEG,
|
JPEG: JimpMime.jpeg,
|
||||||
"PNG": Jimp.MIME_PNG,
|
PNG: JimpMime.png,
|
||||||
"BMP": Jimp.MIME_BMP,
|
BMP: JimpMime.bmp,
|
||||||
"TIFF": Jimp.MIME_TIFF
|
TIFF: JimpMime.tiff,
|
||||||
};
|
};
|
||||||
|
|
||||||
const pngFilterMap = {
|
const pngFilterMap = {
|
||||||
"Auto": Jimp.PNG_FILTER_AUTO,
|
Auto: PNGFilterType.AUTO,
|
||||||
"None": Jimp.PNG_FILTER_NONE,
|
None: PNGFilterType.NONE,
|
||||||
"Sub": Jimp.PNG_FILTER_SUB,
|
Sub: PNGFilterType.SUB,
|
||||||
"Up": Jimp.PNG_FILTER_UP,
|
Up: PNGFilterType.UP,
|
||||||
"Average": Jimp.PNG_FILTER_AVERAGE,
|
Average: PNGFilterType.AVERAGE,
|
||||||
"Paeth": Jimp.PNG_FILTER_PATH
|
Paeth: PNGFilterType.PATH,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mime = formatMap[format];
|
const mime = formatMap[format];
|
||||||
@ -103,18 +91,25 @@ class ConvertImageFormat extends Operation {
|
|||||||
throw new OperationError(`Error opening image file. (${err})`);
|
throw new OperationError(`Error opening image file. (${err})`);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
switch (format) {
|
let buffer;
|
||||||
case "JPEG":
|
switch (mime) {
|
||||||
image.quality(jpegQuality);
|
case JimpMime.jpeg:
|
||||||
|
buffer = await image.getBuffer(mime, {
|
||||||
|
quality: jpegQuality,
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case "PNG":
|
case JimpMime.png:
|
||||||
image.filterType(pngFilterMap[pngFilterType]);
|
buffer = await image.getBuffer(mime, {
|
||||||
image.deflateLevel(pngDeflateLevel);
|
filterType: pngFilterMap[pngFilterType],
|
||||||
|
deflateLevel: pngDeflateLevel,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
buffer = await image.getBuffer(mime);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageBuffer = await image.getBufferAsync(mime);
|
return buffer.buffer;
|
||||||
return imageBuffer.buffer;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error converting image format. (${err})`);
|
throw new OperationError(`Error converting image format. (${err})`);
|
||||||
}
|
}
|
||||||
@ -137,7 +132,6 @@ class ConvertImageFormat extends Operation {
|
|||||||
|
|
||||||
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ConvertImageFormat;
|
export default ConvertImageFormat;
|
||||||
|
|||||||
@ -9,13 +9,18 @@ import OperationError from "../errors/OperationError.mjs";
|
|||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import jimp from "jimp/es/index.js";
|
import {
|
||||||
|
Jimp,
|
||||||
|
JimpMime,
|
||||||
|
ResizeStrategy,
|
||||||
|
HorizontalAlign,
|
||||||
|
VerticalAlign,
|
||||||
|
} from "jimp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cover Image operation
|
* Cover Image operation
|
||||||
*/
|
*/
|
||||||
class CoverImage extends Operation {
|
class CoverImage extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CoverImage constructor
|
* CoverImage constructor
|
||||||
*/
|
*/
|
||||||
@ -24,7 +29,8 @@ class CoverImage extends Operation {
|
|||||||
|
|
||||||
this.name = "Cover Image";
|
this.name = "Cover Image";
|
||||||
this.module = "Image";
|
this.module = "Image";
|
||||||
this.description = "Scales the image to the given width and height, keeping the aspect ratio. The image may be clipped.";
|
this.description =
|
||||||
|
"Scales the image to the given width and height, keeping the aspect ratio. The image may be clipped.";
|
||||||
this.infoURL = "";
|
this.infoURL = "";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "ArrayBuffer";
|
this.outputType = "ArrayBuffer";
|
||||||
@ -34,33 +40,25 @@ class CoverImage extends Operation {
|
|||||||
name: "Width",
|
name: "Width",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 100,
|
value: 100,
|
||||||
min: 1
|
min: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Height",
|
name: "Height",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 100,
|
value: 100,
|
||||||
min: 1
|
min: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Horizontal align",
|
name: "Horizontal align",
|
||||||
type: "option",
|
type: "option",
|
||||||
value: [
|
value: ["Left", "Center", "Right"],
|
||||||
"Left",
|
defaultIndex: 1,
|
||||||
"Center",
|
|
||||||
"Right"
|
|
||||||
],
|
|
||||||
defaultIndex: 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Vertical align",
|
name: "Vertical align",
|
||||||
type: "option",
|
type: "option",
|
||||||
value: [
|
value: ["Top", "Middle", "Bottom"],
|
||||||
"Top",
|
defaultIndex: 1,
|
||||||
"Middle",
|
|
||||||
"Bottom"
|
|
||||||
],
|
|
||||||
defaultIndex: 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Resizing algorithm",
|
name: "Resizing algorithm",
|
||||||
@ -70,10 +68,10 @@ class CoverImage extends Operation {
|
|||||||
"Bilinear",
|
"Bilinear",
|
||||||
"Bicubic",
|
"Bicubic",
|
||||||
"Hermite",
|
"Hermite",
|
||||||
"Bezier"
|
"Bezier",
|
||||||
],
|
],
|
||||||
defaultIndex: 1
|
defaultIndex: 1,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,20 +84,20 @@ class CoverImage extends Operation {
|
|||||||
const [width, height, hAlign, vAlign, alg] = args;
|
const [width, height, hAlign, vAlign, alg] = args;
|
||||||
|
|
||||||
const resizeMap = {
|
const resizeMap = {
|
||||||
"Nearest Neighbour": jimp.RESIZE_NEAREST_NEIGHBOR,
|
"Nearest Neighbour": ResizeStrategy.NEAREST_NEIGHBOR,
|
||||||
"Bilinear": jimp.RESIZE_BILINEAR,
|
Bilinear: ResizeStrategy.BILINEAR,
|
||||||
"Bicubic": jimp.RESIZE_BICUBIC,
|
Bicubic: ResizeStrategy.BICUBIC,
|
||||||
"Hermite": jimp.RESIZE_HERMITE,
|
Hermite: ResizeStrategy.HERMITE,
|
||||||
"Bezier": jimp.RESIZE_BEZIER
|
Bezier: ResizeStrategy.BEZIER,
|
||||||
};
|
};
|
||||||
|
|
||||||
const alignMap = {
|
const alignMap = {
|
||||||
"Left": jimp.HORIZONTAL_ALIGN_LEFT,
|
Left: HorizontalAlign.LEFT,
|
||||||
"Center": jimp.HORIZONTAL_ALIGN_CENTER,
|
Center: HorizontalAlign.CENTER,
|
||||||
"Right": jimp.HORIZONTAL_ALIGN_RIGHT,
|
Right: HorizontalAlign.RIGHT,
|
||||||
"Top": jimp.VERTICAL_ALIGN_TOP,
|
Top: VerticalAlign.TOP,
|
||||||
"Middle": jimp.VERTICAL_ALIGN_MIDDLE,
|
Middle: VerticalAlign.MIDDLE,
|
||||||
"Bottom": jimp.VERTICAL_ALIGN_BOTTOM
|
Bottom: VerticalAlign.BOTTOM,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isImage(input)) {
|
if (!isImage(input)) {
|
||||||
@ -108,19 +106,24 @@ class CoverImage extends Operation {
|
|||||||
|
|
||||||
let image;
|
let image;
|
||||||
try {
|
try {
|
||||||
image = await jimp.read(input);
|
image = await Jimp.read(input);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error loading image. (${err})`);
|
throw new OperationError(`Error loading image. (${err})`);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (isWorkerEnvironment())
|
if (isWorkerEnvironment())
|
||||||
self.sendStatusMessage("Covering image...");
|
self.sendStatusMessage("Covering image...");
|
||||||
image.cover(width, height, alignMap[hAlign] | alignMap[vAlign], resizeMap[alg]);
|
image.cover({
|
||||||
|
w: width,
|
||||||
|
h: height,
|
||||||
|
align: alignMap[hAlign] | alignMap[vAlign],
|
||||||
|
mode: resizeMap[alg],
|
||||||
|
});
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.mime === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
imageBuffer = await image.getBuffer(JimpMime.png);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
imageBuffer = await image.getBuffer(image.mime);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -144,7 +147,6 @@ class CoverImage extends Operation {
|
|||||||
|
|
||||||
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CoverImage;
|
export default CoverImage;
|
||||||
|
|||||||
@ -9,13 +9,12 @@ import OperationError from "../errors/OperationError.mjs";
|
|||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import Jimp from "jimp/es/index.js";
|
import { Jimp, JimpMime } from "jimp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Crop Image operation
|
* Crop Image operation
|
||||||
*/
|
*/
|
||||||
class CropImage extends Operation {
|
class CropImage extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CropImage constructor
|
* CropImage constructor
|
||||||
*/
|
*/
|
||||||
@ -24,7 +23,8 @@ class CropImage extends Operation {
|
|||||||
|
|
||||||
this.name = "Crop Image";
|
this.name = "Crop Image";
|
||||||
this.module = "Image";
|
this.module = "Image";
|
||||||
this.description = "Crops an image to the specified region, or automatically crops edges.<br><br><b><u>Autocrop</u></b><br>Automatically crops same-colour borders from the image.<br><br><u>Autocrop tolerance</u><br>A percentage value for the tolerance of colour difference between pixels.<br><br><u>Only autocrop frames</u><br>Only crop real frames (all sides must have the same border)<br><br><u>Symmetric autocrop</u><br>Force autocrop to be symmetric (top/bottom and left/right are cropped by the same amount)<br><br><u>Autocrop keep border</u><br>The number of pixels of border to leave around the image.";
|
this.description =
|
||||||
|
"Crops an image to the specified region, or automatically crops edges.<br><br><b><u>Autocrop</u></b><br>Automatically crops same-colour borders from the image.<br><br><u>Autocrop tolerance</u><br>A percentage value for the tolerance of colour difference between pixels.<br><br><u>Only autocrop frames</u><br>Only crop real frames (all sides must have the same border)<br><br><u>Symmetric autocrop</u><br>Force autocrop to be symmetric (top/bottom and left/right are cropped by the same amount)<br><br><u>Autocrop keep border</u><br>The number of pixels of border to leave around the image.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Cropping_(image)";
|
this.infoURL = "https://wikipedia.org/wiki/Cropping_(image)";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "ArrayBuffer";
|
this.outputType = "ArrayBuffer";
|
||||||
@ -34,30 +34,30 @@ class CropImage extends Operation {
|
|||||||
name: "X Position",
|
name: "X Position",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 0,
|
value: 0,
|
||||||
min: 0
|
min: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Y Position",
|
name: "Y Position",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 0,
|
value: 0,
|
||||||
min: 0
|
min: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Width",
|
name: "Width",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 10,
|
value: 10,
|
||||||
min: 1
|
min: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Height",
|
name: "Height",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 10,
|
value: 10,
|
||||||
min: 1
|
min: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Autocrop",
|
name: "Autocrop",
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
value: false
|
value: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Autocrop tolerance (%)",
|
name: "Autocrop tolerance (%)",
|
||||||
@ -65,24 +65,24 @@ class CropImage extends Operation {
|
|||||||
value: 0.02,
|
value: 0.02,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 100,
|
max: 100,
|
||||||
step: 0.01
|
step: 0.01,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Only autocrop frames",
|
name: "Only autocrop frames",
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
value: true
|
value: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Symmetric autocrop",
|
name: "Symmetric autocrop",
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
value: false
|
value: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Autocrop keep border (px)",
|
name: "Autocrop keep border (px)",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 0,
|
value: 0,
|
||||||
min: 0
|
min: 0,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +92,17 @@ class CropImage extends Operation {
|
|||||||
* @returns {byteArray}
|
* @returns {byteArray}
|
||||||
*/
|
*/
|
||||||
async run(input, args) {
|
async run(input, args) {
|
||||||
const [xPos, yPos, width, height, autocrop, autoTolerance, autoFrames, autoSymmetric, autoBorder] = args;
|
const [
|
||||||
|
xPos,
|
||||||
|
yPos,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
autocrop,
|
||||||
|
autoTolerance,
|
||||||
|
autoFrames,
|
||||||
|
autoSymmetric,
|
||||||
|
autoBorder,
|
||||||
|
] = args;
|
||||||
if (!isImage(input)) {
|
if (!isImage(input)) {
|
||||||
throw new OperationError("Invalid file type.");
|
throw new OperationError("Invalid file type.");
|
||||||
}
|
}
|
||||||
@ -108,20 +118,25 @@ class CropImage extends Operation {
|
|||||||
self.sendStatusMessage("Cropping image...");
|
self.sendStatusMessage("Cropping image...");
|
||||||
if (autocrop) {
|
if (autocrop) {
|
||||||
image.autocrop({
|
image.autocrop({
|
||||||
tolerance: (autoTolerance / 100),
|
tolerance: autoTolerance / 100,
|
||||||
cropOnlyFrames: autoFrames,
|
cropOnlyFrames: autoFrames,
|
||||||
cropSymmetric: autoSymmetric,
|
cropSymmetric: autoSymmetric,
|
||||||
leaveBorder: autoBorder
|
leaveBorder: autoBorder,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
image.crop(xPos, yPos, width, height);
|
image.crop({
|
||||||
|
x: xPos,
|
||||||
|
y: yPos,
|
||||||
|
w: width,
|
||||||
|
h: height,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.mime === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
imageBuffer = await image.getBuffer(JimpMime.png);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
imageBuffer = await image.getBuffer(image.mime);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -145,7 +160,6 @@ class CropImage extends Operation {
|
|||||||
|
|
||||||
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CropImage;
|
export default CropImage;
|
||||||
|
|||||||
193
src/core/operations/DisassembleARM.mjs
Normal file
193
src/core/operations/DisassembleARM.mjs
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
/**
|
||||||
|
* @author MedjedThomasXM
|
||||||
|
* @copyright Crown Copyright 2024
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
|
import cs from "@alexaltea/capstone-js/dist/capstone.min.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disassemble ARM operation
|
||||||
|
*/
|
||||||
|
class DisassembleARM extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DisassembleARM constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Disassemble ARM";
|
||||||
|
this.module = "Shellcode";
|
||||||
|
this.description = "Disassembles ARM machine code into assembly language.<br><br>Supports ARM (32-bit), Thumb, and ARM64 (AArch64) architectures using the Capstone disassembly framework.<br><br>Input should be in hexadecimal.";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/ARM_architecture_family";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Architecture",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["ARM (32-bit)", "ARM64 (AArch64)"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mode",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["ARM", "Thumb", "Thumb + Cortex-M", "ARMv8"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Endianness",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["Little Endian", "Big Endian"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Starting address (hex)",
|
||||||
|
"type": "number",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Show instruction hex",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Show instruction position",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
async run(input, args) {
|
||||||
|
const [
|
||||||
|
architecture,
|
||||||
|
mode,
|
||||||
|
endianness,
|
||||||
|
startAddress,
|
||||||
|
showHex,
|
||||||
|
showPosition
|
||||||
|
] = args;
|
||||||
|
|
||||||
|
// Remove whitespace from input
|
||||||
|
const hexInput = input.replace(/\s/g, "");
|
||||||
|
|
||||||
|
// Validate hex input
|
||||||
|
if (!/^[0-9a-fA-F]*$/.test(hexInput)) {
|
||||||
|
throw new OperationError("Invalid hexadecimal input. Please provide valid hex characters only.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hexInput.length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hexInput.length % 2 !== 0) {
|
||||||
|
throw new OperationError("Invalid hexadecimal input. Length must be even.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert hex string to byte array
|
||||||
|
const bytes = [];
|
||||||
|
for (let i = 0; i < hexInput.length; i += 2) {
|
||||||
|
bytes.push(parseInt(hexInput.substr(i, 2), 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine architecture constant
|
||||||
|
let arch;
|
||||||
|
if (architecture === "ARM64 (AArch64)") {
|
||||||
|
arch = cs.ARCH_ARM64;
|
||||||
|
} else {
|
||||||
|
arch = cs.ARCH_ARM;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine mode constant
|
||||||
|
let modeValue = cs.MODE_LITTLE_ENDIAN;
|
||||||
|
|
||||||
|
if (architecture === "ARM (32-bit)") {
|
||||||
|
switch (mode) {
|
||||||
|
case "ARM":
|
||||||
|
modeValue = cs.MODE_ARM;
|
||||||
|
break;
|
||||||
|
case "Thumb":
|
||||||
|
modeValue = cs.MODE_THUMB;
|
||||||
|
break;
|
||||||
|
case "Thumb + Cortex-M":
|
||||||
|
modeValue = cs.MODE_THUMB | cs.MODE_MCLASS;
|
||||||
|
break;
|
||||||
|
case "ARMv8":
|
||||||
|
modeValue = cs.MODE_ARM | cs.MODE_V8;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
modeValue = cs.MODE_ARM;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ARM64 only has one mode (ARM mode is default for ARM64)
|
||||||
|
modeValue = cs.MODE_ARM;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add endianness
|
||||||
|
if (endianness === "Big Endian") {
|
||||||
|
modeValue |= cs.MODE_BIG_ENDIAN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isWorkerEnvironment()) {
|
||||||
|
self.sendStatusMessage("Disassembling...");
|
||||||
|
}
|
||||||
|
|
||||||
|
let disassembler;
|
||||||
|
try {
|
||||||
|
disassembler = new cs.Capstone(arch, modeValue);
|
||||||
|
} catch (e) {
|
||||||
|
throw new OperationError(`Failed to initialise Capstone disassembler: ${e}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let instructions;
|
||||||
|
try {
|
||||||
|
instructions = disassembler.disasm(bytes, startAddress);
|
||||||
|
} catch (e) {
|
||||||
|
disassembler.close();
|
||||||
|
// Check if it's a "no valid instructions" error (code 0 means OK but nothing decoded)
|
||||||
|
if (e && e.includes && e.includes("code 0:")) {
|
||||||
|
throw new OperationError(`No valid ${architecture} instructions found in input. The bytes may be for a different architecture or mode.`);
|
||||||
|
}
|
||||||
|
throw new OperationError(`Disassembly failed: ${e}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format output
|
||||||
|
const output = [];
|
||||||
|
for (const insn of instructions) {
|
||||||
|
let line = "";
|
||||||
|
|
||||||
|
if (showPosition) {
|
||||||
|
// Format address as hex with 0x prefix
|
||||||
|
const addrHex = "0x" + insn.address.toString(16).padStart(8, "0");
|
||||||
|
line += addrHex + " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showHex) {
|
||||||
|
// Format instruction bytes as hex
|
||||||
|
const bytesHex = insn.bytes.map(b => b.toString(16).padStart(2, "0")).join("");
|
||||||
|
line += bytesHex.padEnd(16, " ") + " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
line += insn.mnemonic;
|
||||||
|
if (insn.op_str) {
|
||||||
|
line += " " + insn.op_str;
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
disassembler.close();
|
||||||
|
|
||||||
|
return output.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DisassembleARM;
|
||||||
@ -9,13 +9,12 @@ import OperationError from "../errors/OperationError.mjs";
|
|||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import Jimp from "jimp/es/index.js";
|
import { Jimp, JimpMime } from "jimp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image Dither operation
|
* Image Dither operation
|
||||||
*/
|
*/
|
||||||
class DitherImage extends Operation {
|
class DitherImage extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DitherImage constructor
|
* DitherImage constructor
|
||||||
*/
|
*/
|
||||||
@ -51,17 +50,19 @@ class DitherImage extends Operation {
|
|||||||
try {
|
try {
|
||||||
if (isWorkerEnvironment())
|
if (isWorkerEnvironment())
|
||||||
self.sendStatusMessage("Applying dither to image...");
|
self.sendStatusMessage("Applying dither to image...");
|
||||||
image.dither565();
|
image.dither();
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.mime === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
imageBuffer = await image.getBuffer(JimpMime.png);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
imageBuffer = await image.getBuffer(image.mime);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error applying dither to image. (${err})`);
|
throw new OperationError(
|
||||||
|
`Error applying dither to image. (${err})`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +82,6 @@ class DitherImage extends Operation {
|
|||||||
|
|
||||||
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DitherImage;
|
export default DitherImage;
|
||||||
|
|||||||
@ -44,23 +44,6 @@ class EscapeUnicodeCharacters extends Operation {
|
|||||||
"value": true
|
"value": true
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
this.checks = [
|
|
||||||
{
|
|
||||||
pattern: "\\\\u(?:[\\da-f]{4,6})",
|
|
||||||
flags: "i",
|
|
||||||
args: ["\\u"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "%u(?:[\\da-f]{4,6})",
|
|
||||||
flags: "i",
|
|
||||||
args: ["%u"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "U\\+(?:[\\da-f]{4,6})",
|
|
||||||
flags: "i",
|
|
||||||
args: ["U+"]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
175
src/core/operations/ExtractAudioMetadata.mjs
Normal file
175
src/core/operations/ExtractAudioMetadata.mjs
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
/**
|
||||||
|
* @author d0s1nt [d0s1nt@cyberchefaudio]
|
||||||
|
* @copyright Crown Copyright 2025
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import { makeEmptyReport, sniffContainer } from "../lib/AudioMetaSchema.mjs";
|
||||||
|
import {
|
||||||
|
parseMp3, parseRiffWave, parseFlac, parseOgg,
|
||||||
|
parseMp4BestEffort, parseAiffBestEffort,
|
||||||
|
parseAacAdts, parseAc3, parseWmaAsf,
|
||||||
|
} from "../lib/AudioParsers.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract Audio Metadata operation.
|
||||||
|
*/
|
||||||
|
class ExtractAudioMetadata extends Operation {
|
||||||
|
/** Creates the Extract Audio Metadata operation. */
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Extract Audio Metadata";
|
||||||
|
this.module = "Default";
|
||||||
|
this.description =
|
||||||
|
"Extract common audio metadata across MP3 (ID3v2/ID3v1/GEOB), WAV/BWF/BW64 (INFO/bext/iXML/axml), FLAC (Vorbis Comment/Picture), OGG (Vorbis/OpusTags), AAC (ADTS), AC3 (Dolby Digital), WMA (ASF), plus best-effort MP4/M4A and AIFF scanning. Outputs normalized JSON.";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/Audio_file_format";
|
||||||
|
this.inputType = "ArrayBuffer";
|
||||||
|
this.outputType = "JSON";
|
||||||
|
this.presentType = "html";
|
||||||
|
|
||||||
|
this.args = [
|
||||||
|
{ name: "Filename (optional)", type: "string", value: "" },
|
||||||
|
{ name: "Max embedded text bytes (iXML/axml/etc)", type: "number", value: 1024 * 512 },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ArrayBuffer} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const filename = (args?.[0] || "").trim() || null;
|
||||||
|
const maxTextBytes = Number.isFinite(args?.[1]) ? Math.max(1024, args[1]) : 1024 * 512;
|
||||||
|
|
||||||
|
if (!(input instanceof ArrayBuffer) || input.byteLength === 0)
|
||||||
|
throw new OperationError("No input data. Load an audio file (drag/drop or use the open file button).");
|
||||||
|
|
||||||
|
const bytes = new Uint8Array(input);
|
||||||
|
const container = sniffContainer(bytes);
|
||||||
|
const report = makeEmptyReport(filename, bytes.length, container);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsers = {
|
||||||
|
mp3: () => parseMp3(bytes, report),
|
||||||
|
wav: () => parseRiffWave(bytes, report, maxTextBytes),
|
||||||
|
bw64: () => parseRiffWave(bytes, report, maxTextBytes),
|
||||||
|
flac: () => parseFlac(bytes, report, maxTextBytes),
|
||||||
|
ogg: () => parseOgg(bytes, report),
|
||||||
|
opus: () => parseOgg(bytes, report),
|
||||||
|
mp4: () => parseMp4BestEffort(bytes, report),
|
||||||
|
m4a: () => parseMp4BestEffort(bytes, report),
|
||||||
|
aiff: () => parseAiffBestEffort(bytes, report, maxTextBytes),
|
||||||
|
aac: () => parseAacAdts(bytes, report),
|
||||||
|
ac3: () => parseAc3(bytes, report),
|
||||||
|
wma: () => parseWmaAsf(bytes, report),
|
||||||
|
};
|
||||||
|
if (parsers[container.type]) {
|
||||||
|
parsers[container.type]();
|
||||||
|
} else {
|
||||||
|
report.errors.push({ stage: "sniff", message: "Unknown/unsupported container (best-effort scan not implemented)." });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
report.errors.push({ stage: "parse", message: String(e?.message || e) });
|
||||||
|
}
|
||||||
|
|
||||||
|
return report;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Renders the extracted metadata as an HTML table. */
|
||||||
|
present(data) {
|
||||||
|
if (!data || typeof data !== "object") return JSON.stringify(data, null, 4);
|
||||||
|
|
||||||
|
const esc = Utils.escapeHtml;
|
||||||
|
const row = (k, v) => `<tr><td>${esc(String(k))}</td><td>${esc(String(v ?? ""))}</td></tr>\n`;
|
||||||
|
const section = (title) => `<tr><th colspan="2" style="background:#e9ecef;text-align:center">${esc(title)}</th></tr>\n`;
|
||||||
|
const objRows = (obj, filter = (v) => v !== null) => {
|
||||||
|
for (const [k, v] of Object.entries(obj)) {
|
||||||
|
if (filter(v)) html += row(k, v);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const objSection = (obj, title, filter) => {
|
||||||
|
if (!obj) return;
|
||||||
|
html += section(title);
|
||||||
|
objRows(obj, filter);
|
||||||
|
};
|
||||||
|
const listSection = (arr, title, fmt) => {
|
||||||
|
if (!arr?.length) return;
|
||||||
|
html += section(title);
|
||||||
|
for (const item of arr) html += fmt(item);
|
||||||
|
};
|
||||||
|
|
||||||
|
let html = `<table class="table table-hover table-sm table-bordered table-nonfluid">\n`;
|
||||||
|
|
||||||
|
html += section("Artifact");
|
||||||
|
html += row("Filename", data.artifact?.filename || "(none)");
|
||||||
|
html += row("Size", `${(data.artifact?.byte_length ?? 0).toLocaleString()} bytes`);
|
||||||
|
html += row("Container", data.artifact?.container?.type);
|
||||||
|
html += row("MIME", data.artifact?.container?.mime);
|
||||||
|
if (data.artifact?.container?.brand) html += row("Brand", data.artifact.container.brand);
|
||||||
|
|
||||||
|
html += section("Detections");
|
||||||
|
html += row("Metadata systems", (data.detections?.metadata_systems || []).join(", ") || "None");
|
||||||
|
html += row("Provenance systems", (data.detections?.provenance_systems || []).join(", ") || "None");
|
||||||
|
|
||||||
|
const common = data.tags?.common || {};
|
||||||
|
html += section("Common Tags");
|
||||||
|
if (Object.values(common).some((v) => v !== null)) {
|
||||||
|
for (const [key, val] of Object.entries(common)) {
|
||||||
|
if (val !== null) html += row(key.charAt(0).toUpperCase() + key.slice(1), val);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
html += row("(none)", "No common tags found");
|
||||||
|
}
|
||||||
|
|
||||||
|
listSection(data.tags?.raw?.id3v2?.frames, "ID3v2 Frames", (f) => {
|
||||||
|
const val = typeof f.decoded === "object" ? JSON.stringify(f.decoded) : (f.decoded ?? `(${f.size} bytes)`);
|
||||||
|
return row(f.id + (f.description ? ` \u2014 ${f.description}` : ""), val);
|
||||||
|
});
|
||||||
|
objSection(data.tags?.raw?.id3v1, "ID3v1", (v) => !!v);
|
||||||
|
listSection(data.tags?.raw?.apev2?.items, "APEv2 Tags", (i) => row(i.key, i.value));
|
||||||
|
|
||||||
|
if (data.tags?.raw?.vorbis_comments?.comments?.length) {
|
||||||
|
html += section("Vorbis Comments");
|
||||||
|
html += row("Vendor", data.tags.raw.vorbis_comments.vendor);
|
||||||
|
for (const c of data.tags.raw.vorbis_comments.comments) html += row(c.key, c.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
objSection(data.tags?.raw?.riff?.info, "RIFF INFO", () => true);
|
||||||
|
objSection(data.tags?.raw?.riff?.bext, "BWF bext");
|
||||||
|
listSection(data.tags?.raw?.riff?.chunks, "RIFF Chunks", (c) => row(c.id, `${c.size} bytes @ offset ${c.offset}`));
|
||||||
|
listSection(data.tags?.raw?.flac?.blocks, "FLAC Metadata Blocks", (b) => row(b.type, `${b.length} bytes`));
|
||||||
|
|
||||||
|
if (data.tags?.raw?.mp4?.top_level_atoms?.length) {
|
||||||
|
html += section("MP4 Top-Level Atoms");
|
||||||
|
const atoms = data.tags.raw.mp4.top_level_atoms;
|
||||||
|
for (const a of atoms.slice(0, 50)) html += row(a.type, `${a.size} bytes @ offset ${a.offset}`);
|
||||||
|
if (atoms.length > 50) html += row("...", `${atoms.length - 50} more atoms`);
|
||||||
|
}
|
||||||
|
|
||||||
|
listSection(data.tags?.raw?.aiff?.chunks, "AIFF Chunks", (c) => row(c.id, c.value));
|
||||||
|
objSection(data.tags?.raw?.aac, "AAC ADTS");
|
||||||
|
objSection(data.tags?.raw?.ac3, "AC3 (Dolby Digital)");
|
||||||
|
objSection(data.tags?.raw?.asf?.content_description, "ASF Content Description", (v) => !!v);
|
||||||
|
listSection(data.tags?.raw?.asf?.extended_content, "ASF Extended Content", (d) => row(d.name, d.value));
|
||||||
|
listSection(data.embedded, "Embedded Objects", (e) => row(e.id, `${e.content_type || "unknown"} \u2014 ${(e.byte_length ?? 0).toLocaleString()} bytes`));
|
||||||
|
|
||||||
|
if (data.provenance?.c2pa?.present) {
|
||||||
|
html += section("C2PA Provenance");
|
||||||
|
html += row("Present", "Yes");
|
||||||
|
for (const emb of (data.provenance.c2pa.embedding || []))
|
||||||
|
html += row("Carrier", `${emb.carrier} \u2014 ${(emb.byte_length ?? 0).toLocaleString()} bytes`);
|
||||||
|
}
|
||||||
|
|
||||||
|
listSection(data.errors, "Errors", (e) => row(e.stage, e.message));
|
||||||
|
|
||||||
|
html += "</table>";
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExtractAudioMetadata;
|
||||||
@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.mjs";
|
||||||
import { search } from "../lib/Extract.mjs";
|
import { EMAIL_REGEX, search } from "../lib/Extract.mjs";
|
||||||
import { caseInsensitiveSort } from "../lib/Sort.mjs";
|
import { caseInsensitiveSort } from "../lib/Sort.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,8 +50,7 @@ class ExtractEmailAddresses extends Operation {
|
|||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const [displayTotal, sort, unique] = args,
|
const [displayTotal, sort, unique] = args,
|
||||||
// email regex from: https://www.regextester.com/98066
|
regex = EMAIL_REGEX;
|
||||||
regex = /(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?\.)+[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\])/ig;
|
|
||||||
|
|
||||||
const results = search(
|
const results = search(
|
||||||
input,
|
input,
|
||||||
|
|||||||
@ -9,13 +9,12 @@ import OperationError from "../errors/OperationError.mjs";
|
|||||||
import Utils from "../Utils.mjs";
|
import Utils from "../Utils.mjs";
|
||||||
import { fromBinary } from "../lib/Binary.mjs";
|
import { fromBinary } from "../lib/Binary.mjs";
|
||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import Jimp from "jimp/es/index.js";
|
import { Jimp } from "jimp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract LSB operation
|
* Extract LSB operation
|
||||||
*/
|
*/
|
||||||
class ExtractLSB extends Operation {
|
class ExtractLSB extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ExtractLSB constructor
|
* ExtractLSB constructor
|
||||||
*/
|
*/
|
||||||
@ -24,8 +23,10 @@ class ExtractLSB extends Operation {
|
|||||||
|
|
||||||
this.name = "Extract LSB";
|
this.name = "Extract LSB";
|
||||||
this.module = "Image";
|
this.module = "Image";
|
||||||
this.description = "Extracts the Least Significant Bit data from each pixel in an image. This is a common way to hide data in Steganography.";
|
this.description =
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Bit_numbering#Least_significant_bit_in_digital_steganography";
|
"Extracts the Least Significant Bit data from each pixel in an image. This is a common way to hide data in Steganography.";
|
||||||
|
this.infoURL =
|
||||||
|
"https://wikipedia.org/wiki/Bit_numbering#Least_significant_bit_in_digital_steganography";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "byteArray";
|
this.outputType = "byteArray";
|
||||||
this.args = [
|
this.args = [
|
||||||
@ -57,8 +58,8 @@ class ExtractLSB extends Operation {
|
|||||||
{
|
{
|
||||||
name: "Bit",
|
name: "Bit",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 0
|
value: 0,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,21 +69,27 @@ class ExtractLSB extends Operation {
|
|||||||
* @returns {byteArray}
|
* @returns {byteArray}
|
||||||
*/
|
*/
|
||||||
async run(input, args) {
|
async run(input, args) {
|
||||||
if (!isImage(input)) throw new OperationError("Please enter a valid image file.");
|
if (!isImage(input))
|
||||||
|
throw new OperationError("Please enter a valid image file.");
|
||||||
|
|
||||||
const bit = 7 - args.pop(),
|
const bit = 7 - args.pop(),
|
||||||
pixelOrder = args.pop(),
|
pixelOrder = args.pop(),
|
||||||
colours = args.filter(option => option !== "").map(option => COLOUR_OPTIONS.indexOf(option)),
|
colours = args
|
||||||
|
.filter((option) => option !== "")
|
||||||
|
.map((option) => COLOUR_OPTIONS.indexOf(option)),
|
||||||
parsedImage = await Jimp.read(input),
|
parsedImage = await Jimp.read(input),
|
||||||
width = parsedImage.bitmap.width,
|
width = parsedImage.bitmap.width,
|
||||||
height = parsedImage.bitmap.height,
|
height = parsedImage.bitmap.height,
|
||||||
rgba = parsedImage.bitmap.data;
|
rgba = parsedImage.bitmap.data;
|
||||||
|
|
||||||
if (bit < 0 || bit > 7) {
|
if (bit < 0 || bit > 7) {
|
||||||
throw new OperationError("Error: Bit argument must be between 0 and 7");
|
throw new OperationError(
|
||||||
|
"Error: Bit argument must be between 0 and 7",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let i, combinedBinary = "";
|
let i,
|
||||||
|
combinedBinary = "";
|
||||||
|
|
||||||
if (pixelOrder === "Row") {
|
if (pixelOrder === "Row") {
|
||||||
for (i = 0; i < rgba.length; i += 4) {
|
for (i = 0; i < rgba.length; i += 4) {
|
||||||
@ -106,7 +113,6 @@ class ExtractLSB extends Operation {
|
|||||||
|
|
||||||
return fromBinary(combinedBinary);
|
return fromBinary(combinedBinary);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const COLOUR_OPTIONS = ["R", "G", "B", "A"];
|
const COLOUR_OPTIONS = ["R", "G", "B", "A"];
|
||||||
|
|||||||
@ -7,15 +7,14 @@
|
|||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.mjs";
|
||||||
import OperationError from "../errors/OperationError.mjs";
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import Jimp from "jimp/es/index.js";
|
import { Jimp } from "jimp";
|
||||||
|
|
||||||
import {RGBA_DELIM_OPTIONS} from "../lib/Delim.mjs";
|
import { RGBA_DELIM_OPTIONS } from "../lib/Delim.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract RGBA operation
|
* Extract RGBA operation
|
||||||
*/
|
*/
|
||||||
class ExtractRGBA extends Operation {
|
class ExtractRGBA extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ExtractRGBA constructor
|
* ExtractRGBA constructor
|
||||||
*/
|
*/
|
||||||
@ -24,7 +23,8 @@ class ExtractRGBA extends Operation {
|
|||||||
|
|
||||||
this.name = "Extract RGBA";
|
this.name = "Extract RGBA";
|
||||||
this.module = "Image";
|
this.module = "Image";
|
||||||
this.description = "Extracts each pixel's RGBA value in an image. These are sometimes used in Steganography to hide text or data.";
|
this.description =
|
||||||
|
"Extracts each pixel's RGBA value in an image. These are sometimes used in Steganography to hide text or data.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/RGBA_color_space";
|
this.infoURL = "https://wikipedia.org/wiki/RGBA_color_space";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "string";
|
this.outputType = "string";
|
||||||
@ -32,13 +32,13 @@ class ExtractRGBA extends Operation {
|
|||||||
{
|
{
|
||||||
name: "Delimiter",
|
name: "Delimiter",
|
||||||
type: "editableOption",
|
type: "editableOption",
|
||||||
value: RGBA_DELIM_OPTIONS
|
value: RGBA_DELIM_OPTIONS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Include Alpha",
|
name: "Include Alpha",
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
value: true
|
value: true,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,18 +48,20 @@ class ExtractRGBA extends Operation {
|
|||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
async run(input, args) {
|
async run(input, args) {
|
||||||
if (!isImage(input)) throw new OperationError("Please enter a valid image file.");
|
if (!isImage(input))
|
||||||
|
throw new OperationError("Please enter a valid image file.");
|
||||||
|
|
||||||
const delimiter = args[0],
|
const delimiter = args[0],
|
||||||
includeAlpha = args[1],
|
includeAlpha = args[1],
|
||||||
parsedImage = await Jimp.read(input);
|
parsedImage = await Jimp.read(input);
|
||||||
|
|
||||||
let bitmap = parsedImage.bitmap.data;
|
let bitmap = parsedImage.bitmap.data;
|
||||||
bitmap = includeAlpha ? bitmap : bitmap.filter((val, idx) => idx % 4 !== 3);
|
bitmap = includeAlpha ?
|
||||||
|
bitmap :
|
||||||
|
bitmap.filter((val, idx) => idx % 4 !== 3);
|
||||||
|
|
||||||
return bitmap.join(delimiter);
|
return bitmap.join(delimiter);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ExtractRGBA;
|
export default ExtractRGBA;
|
||||||
|
|||||||
80
src/core/operations/FlaskSessionDecode.mjs
Normal file
80
src/core/operations/FlaskSessionDecode.mjs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* @author ThePlayer372-FR []
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import { fromBase64 } from "../lib/Base64.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flask Session Decode operation
|
||||||
|
*/
|
||||||
|
class FlaskSessionDecode extends Operation {
|
||||||
|
/**
|
||||||
|
* FlaskSessionDecode constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Flask Session Decode";
|
||||||
|
this.module = "Crypto";
|
||||||
|
this.description = "Decodes the payload of a Flask session cookie (itsdangerous) into JSON.";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "JSON";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
name: "View TimeStamp",
|
||||||
|
type: "boolean",
|
||||||
|
value: false
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {Object[]}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
input = input.trim();
|
||||||
|
const parts = input.split(".");
|
||||||
|
if (parts.length !== 3) {
|
||||||
|
throw new OperationError("Invalid Flask token format. Expected payload.timestamp.signature");
|
||||||
|
}
|
||||||
|
|
||||||
|
const payloadB64 = parts[0];
|
||||||
|
const time = parts[1];
|
||||||
|
|
||||||
|
const timeB64 = time.replace(/-/g, "+").replace(/_/g, "/");
|
||||||
|
const binary = fromBase64(timeB64);
|
||||||
|
const bytes = new Uint8Array(4);
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
bytes[i] = binary.charCodeAt(i);
|
||||||
|
}
|
||||||
|
const view = new DataView(bytes.buffer);
|
||||||
|
const timestamp = view.getInt32(0, false);
|
||||||
|
|
||||||
|
const base64 = payloadB64.replace(/-/g, "+").replace(/_/g, "/");
|
||||||
|
const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, "=");
|
||||||
|
let payloadJson;
|
||||||
|
try {
|
||||||
|
payloadJson = fromBase64(padded);
|
||||||
|
} catch (e) {
|
||||||
|
throw new OperationError("Invalid Base64 payload");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let data = JSON.parse(payloadJson);
|
||||||
|
|
||||||
|
if (args[0]) {
|
||||||
|
data = {payload: data, timestamp: timestamp};
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
} catch (e) {
|
||||||
|
throw new OperationError("Unable to decode JSON payload: " + e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FlaskSessionDecode;
|
||||||
89
src/core/operations/FlaskSessionSign.mjs
Normal file
89
src/core/operations/FlaskSessionSign.mjs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/**
|
||||||
|
* @author ThePlayer372-FR []
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import CryptoApi from "crypto-api/src/crypto-api.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flask Session Sign operation
|
||||||
|
*/
|
||||||
|
class FlaskSessionSign extends Operation {
|
||||||
|
/**
|
||||||
|
* FlaskSessionSign constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Flask Session Sign";
|
||||||
|
this.module = "Crypto";
|
||||||
|
this.description = "Signs a JSON payload to produce a Flask session cookie (itsdangerous HMAC).";
|
||||||
|
this.inputType = "JSON";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
name: "Key",
|
||||||
|
type: "toggleString",
|
||||||
|
value: "",
|
||||||
|
toggleValues: ["Hex", "Decimal", "Binary", "Base64", "UTF8", "Latin1"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Salt",
|
||||||
|
type: "toggleString",
|
||||||
|
value: "cookie-session",
|
||||||
|
toggleValues: ["UTF8", "Hex", "Decimal", "Binary", "Base64", "Latin1"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Algorithm",
|
||||||
|
type: "option",
|
||||||
|
value: ["sha1", "sha256"],
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
if (!args[0].string) {
|
||||||
|
throw new OperationError("Secret key required");
|
||||||
|
}
|
||||||
|
const key = Utils.convertToByteString(args[0].string, args[0].option);
|
||||||
|
const salt = Utils.convertToByteString(args[1].string || "cookie-session", args[1].option);
|
||||||
|
const algorithm = args[2] || "sha1";
|
||||||
|
|
||||||
|
const payloadB64 = toBase64(Utils.strToByteArray(JSON.stringify(input)));
|
||||||
|
const payload = payloadB64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
||||||
|
|
||||||
|
const derivedKey = CryptoApi.getHmac(key, CryptoApi.getHasher(algorithm));
|
||||||
|
derivedKey.update(salt);
|
||||||
|
|
||||||
|
const currentTimeStamp = Math.ceil(Date.now() / 1000);
|
||||||
|
const buffer = new ArrayBuffer(4);
|
||||||
|
const view = new DataView(buffer);
|
||||||
|
view.setInt32(0, currentTimeStamp, false);
|
||||||
|
const bytes = new Uint8Array(buffer);
|
||||||
|
let binary = "";
|
||||||
|
bytes.forEach(b => binary += String.fromCharCode(b));
|
||||||
|
const timeB64 = toBase64(Utils.strToByteArray(binary));
|
||||||
|
const time = timeB64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
||||||
|
|
||||||
|
const data = Utils.convertToByteString(payload + "." + time, "utf8");
|
||||||
|
const sign = CryptoApi.getHmac(derivedKey.finalize(), CryptoApi.getHasher(algorithm));
|
||||||
|
sign.update(data);
|
||||||
|
|
||||||
|
const signB64 = toBase64(sign.finalize());
|
||||||
|
const sign64 = signB64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
||||||
|
|
||||||
|
return payload + "." + time + "." + sign64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default FlaskSessionSign;
|
||||||
136
src/core/operations/FlaskSessionVerify.mjs
Normal file
136
src/core/operations/FlaskSessionVerify.mjs
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
/**
|
||||||
|
* @author ThePlayer372-FR []
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import CryptoApi from "crypto-api/src/crypto-api.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import { toBase64, fromBase64 } from "../lib/Base64.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flask Session Verify operation
|
||||||
|
*/
|
||||||
|
class FlaskSessionVerify extends Operation {
|
||||||
|
/**
|
||||||
|
* FlaskSessionVerify constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Flask Session Verify";
|
||||||
|
this.module = "Crypto";
|
||||||
|
this.description = "Verifies the HMAC signature of a Flask session cookie (itsdangerous) generated.";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "JSON";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
name: "Key",
|
||||||
|
type: "toggleString",
|
||||||
|
value: "",
|
||||||
|
toggleValues: ["Hex", "Decimal", "Binary", "Base64", "UTF8", "Latin1"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Salt",
|
||||||
|
type: "toggleString",
|
||||||
|
value: "cookie-session",
|
||||||
|
toggleValues: ["UTF8", "Hex", "Decimal", "Binary", "Base64", "Latin1"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Algorithm",
|
||||||
|
type: "option",
|
||||||
|
value: ["sha1", "sha256"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "View TimeStamp",
|
||||||
|
type: "boolean",
|
||||||
|
value: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
|
||||||
|
if (!args[0].string) {
|
||||||
|
throw new OperationError("Secret key required");
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = Utils.convertToByteString(args[0].string, args[0].option);
|
||||||
|
const salt = Utils.convertToByteString(args[1].string || "cookie-session", args[1].option);
|
||||||
|
const algorithm = args[2] || "sha1";
|
||||||
|
|
||||||
|
input = input.trim();
|
||||||
|
|
||||||
|
const parts = input.split(".");
|
||||||
|
|
||||||
|
if (parts.length !== 3) {
|
||||||
|
throw new OperationError("Invalid Flask token format. Expected payload.timestamp.signature");
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = Utils.convertToByteString(parts[0] + "." + parts[1], "utf8");
|
||||||
|
|
||||||
|
|
||||||
|
const derivedKey = CryptoApi.getHmac(key, CryptoApi.getHasher(algorithm));
|
||||||
|
derivedKey.update(salt);
|
||||||
|
|
||||||
|
const sign = CryptoApi.getHmac(derivedKey.finalize(), CryptoApi.getHasher(algorithm));
|
||||||
|
sign.update(data);
|
||||||
|
|
||||||
|
const payloadB64 = parts[0];
|
||||||
|
const base64 = payloadB64.replace(/-/g, "+").replace(/_/g, "/");
|
||||||
|
const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, "=");
|
||||||
|
|
||||||
|
const time = parts[1];
|
||||||
|
|
||||||
|
const timeB64 = time.replace(/-/g, "+").replace(/_/g, "/");
|
||||||
|
const binary = fromBase64(timeB64);
|
||||||
|
const bytes = new Uint8Array(4);
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
bytes[i] = binary.charCodeAt(i);
|
||||||
|
}
|
||||||
|
const view = new DataView(bytes.buffer);
|
||||||
|
const timestamp = view.getInt32(0, false);
|
||||||
|
|
||||||
|
let payloadJson;
|
||||||
|
try {
|
||||||
|
payloadJson = fromBase64(padded);
|
||||||
|
} catch (e) {
|
||||||
|
throw new OperationError("Invalid Base64 payload");
|
||||||
|
}
|
||||||
|
|
||||||
|
const signB64 = toBase64(sign.finalize());
|
||||||
|
const sign64 = signB64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
||||||
|
|
||||||
|
if (sign64 !== parts[2]) {
|
||||||
|
throw new OperationError("Invalid signature!");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decoded = JSON.parse(payloadJson);
|
||||||
|
if (!args[3]) {
|
||||||
|
return {
|
||||||
|
valid: true,
|
||||||
|
payload: decoded,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
valid: true,
|
||||||
|
payload: decoded,
|
||||||
|
timestamp: timestamp
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw new OperationError("Unable to decode JSON payload: " + e.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default FlaskSessionVerify;
|
||||||
@ -9,13 +9,12 @@ import OperationError from "../errors/OperationError.mjs";
|
|||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import Jimp from "jimp/es/index.js";
|
import { Jimp, JimpMime } from "jimp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flip Image operation
|
* Flip Image operation
|
||||||
*/
|
*/
|
||||||
class FlipImage extends Operation {
|
class FlipImage extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FlipImage constructor
|
* FlipImage constructor
|
||||||
*/
|
*/
|
||||||
@ -33,8 +32,8 @@ class FlipImage extends Operation {
|
|||||||
{
|
{
|
||||||
name: "Axis",
|
name: "Axis",
|
||||||
type: "option",
|
type: "option",
|
||||||
value: ["Horizontal", "Vertical"]
|
value: ["Horizontal", "Vertical"],
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,18 +59,24 @@ class FlipImage extends Operation {
|
|||||||
self.sendStatusMessage("Flipping image...");
|
self.sendStatusMessage("Flipping image...");
|
||||||
switch (flipAxis) {
|
switch (flipAxis) {
|
||||||
case "Horizontal":
|
case "Horizontal":
|
||||||
image.flip(true, false);
|
image.flip({
|
||||||
|
horizontal: true,
|
||||||
|
vertical: false,
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case "Vertical":
|
case "Vertical":
|
||||||
image.flip(false, true);
|
image.flip({
|
||||||
|
horizontal: false,
|
||||||
|
vertical: true,
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.mime === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
imageBuffer = await image.getBuffer(JimpMime.png);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
imageBuffer = await image.getBuffer(image.mime);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -95,7 +100,6 @@ class FlipImage extends Operation {
|
|||||||
|
|
||||||
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FlipImage;
|
export default FlipImage;
|
||||||
|
|||||||
@ -23,7 +23,7 @@ class FromQuotedPrintable extends Operation {
|
|||||||
|
|
||||||
this.name = "From Quoted Printable";
|
this.name = "From Quoted Printable";
|
||||||
this.module = "Default";
|
this.module = "Default";
|
||||||
this.description = "Converts QP-encoded text back to standard text.<br><br>e.g. The quoted-printable encoded string <code>hello=20world</code> becomes <code>hello world</code>";
|
this.description = "Converts QP-encoded text back to standard text. This format is a content transfer encoding common in email messages.<br><br>e.g. The quoted-printable encoded string <code>hello=20world</code> becomes <code>hello world</code>";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Quoted-printable";
|
this.infoURL = "https://wikipedia.org/wiki/Quoted-printable";
|
||||||
this.inputType = "string";
|
this.inputType = "string";
|
||||||
this.outputType = "byteArray";
|
this.outputType = "byteArray";
|
||||||
|
|||||||
@ -7,16 +7,15 @@
|
|||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.mjs";
|
||||||
import OperationError from "../errors/OperationError.mjs";
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
import Utils from "../Utils.mjs";
|
import Utils from "../Utils.mjs";
|
||||||
import {isImage} from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import {toBase64} from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import {isWorkerEnvironment} from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import Jimp from "jimp/es/index.js";
|
import { Jimp, JimpMime, ResizeStrategy, rgbaToInt } from "jimp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate Image operation
|
* Generate Image operation
|
||||||
*/
|
*/
|
||||||
class GenerateImage extends Operation {
|
class GenerateImage extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GenerateImage constructor
|
* GenerateImage constructor
|
||||||
*/
|
*/
|
||||||
@ -25,27 +24,28 @@ class GenerateImage extends Operation {
|
|||||||
|
|
||||||
this.name = "Generate Image";
|
this.name = "Generate Image";
|
||||||
this.module = "Image";
|
this.module = "Image";
|
||||||
this.description = "Generates an image using the input as pixel values.";
|
this.description =
|
||||||
|
"Generates an image using the input as pixel values.";
|
||||||
this.infoURL = "";
|
this.infoURL = "";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "ArrayBuffer";
|
this.outputType = "ArrayBuffer";
|
||||||
this.presentType = "html";
|
this.presentType = "html";
|
||||||
this.args = [
|
this.args = [
|
||||||
{
|
{
|
||||||
"name": "Mode",
|
name: "Mode",
|
||||||
"type": "option",
|
type: "option",
|
||||||
"value": ["Greyscale", "RG", "RGB", "RGBA", "Bits"]
|
value: ["Greyscale", "RG", "RGB", "RGBA", "Bits"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Pixel Scale Factor",
|
name: "Pixel Scale Factor",
|
||||||
"type": "number",
|
type: "number",
|
||||||
"value": 8,
|
value: 8,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Pixels per row",
|
name: "Pixels per row",
|
||||||
"type": "number",
|
type: "number",
|
||||||
"value": 64,
|
value: 64,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,21 +67,23 @@ class GenerateImage extends Operation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const bytePerPixelMap = {
|
const bytePerPixelMap = {
|
||||||
"Greyscale": 1,
|
Greyscale: 1,
|
||||||
"RG": 2,
|
RG: 2,
|
||||||
"RGB": 3,
|
RGB: 3,
|
||||||
"RGBA": 4,
|
RGBA: 4,
|
||||||
"Bits": 1/8,
|
Bits: 1 / 8,
|
||||||
};
|
};
|
||||||
|
|
||||||
const bytesPerPixel = bytePerPixelMap[mode];
|
const bytesPerPixel = bytePerPixelMap[mode];
|
||||||
|
|
||||||
if (bytesPerPixel > 0 && input.length % bytesPerPixel !== 0) {
|
if (bytesPerPixel > 0 && input.length % bytesPerPixel !== 0) {
|
||||||
throw new OperationError(`Number of bytes is not a divisor of ${bytesPerPixel}`);
|
throw new OperationError(
|
||||||
|
`Number of bytes is not a divisor of ${bytesPerPixel}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const height = Math.ceil(input.length / bytesPerPixel / width);
|
const height = Math.ceil(input.length / bytesPerPixel / width);
|
||||||
const image = await new Jimp(width, height, (err, image) => {});
|
const image = new Jimp({ width, height });
|
||||||
|
|
||||||
if (isWorkerEnvironment())
|
if (isWorkerEnvironment())
|
||||||
self.sendStatusMessage("Generating image from data...");
|
self.sendStatusMessage("Generating image from data...");
|
||||||
@ -94,8 +96,8 @@ class GenerateImage extends Operation {
|
|||||||
const x = index % width;
|
const x = index % width;
|
||||||
const y = Math.floor(index / width);
|
const y = Math.floor(index / width);
|
||||||
|
|
||||||
const value = curByte[k] === "0" ? 0xFF : 0x00;
|
const value = curByte[k] === "0" ? 0xff : 0x00;
|
||||||
const pixel = Jimp.rgbaToInt(value, value, value, 0xFF);
|
const pixel = rgbaToInt(value, value, value, 0xff);
|
||||||
image.setPixelColor(pixel, x, y);
|
image.setPixelColor(pixel, x, y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,7 +111,7 @@ class GenerateImage extends Operation {
|
|||||||
let red = 0x00;
|
let red = 0x00;
|
||||||
let green = 0x00;
|
let green = 0x00;
|
||||||
let blue = 0x00;
|
let blue = 0x00;
|
||||||
let alpha = 0xFF;
|
let alpha = 0xff;
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case "Greyscale":
|
case "Greyscale":
|
||||||
@ -139,10 +141,12 @@ class GenerateImage extends Operation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const pixel = Jimp.rgbaToInt(red, green, blue, alpha);
|
const pixel = rgbaToInt(red, green, blue, alpha);
|
||||||
image.setPixelColor(pixel, x, y);
|
image.setPixelColor(pixel, x, y);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error while generating image from pixel values. (${err})`);
|
throw new OperationError(
|
||||||
|
`Error while generating image from pixel values. (${err})`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,11 +155,15 @@ class GenerateImage extends Operation {
|
|||||||
if (isWorkerEnvironment())
|
if (isWorkerEnvironment())
|
||||||
self.sendStatusMessage("Scaling image...");
|
self.sendStatusMessage("Scaling image...");
|
||||||
|
|
||||||
image.scaleToFit(width*scale, height*scale, Jimp.RESIZE_NEAREST_NEIGHBOR);
|
image.scaleToFit({
|
||||||
|
w: width * scale,
|
||||||
|
h: height * scale,
|
||||||
|
mode: ResizeStrategy.NEAREST_NEIGHBOR,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
const imageBuffer = await image.getBuffer(JimpMime.png);
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error generating image. (${err})`);
|
throw new OperationError(`Error generating image. (${err})`);
|
||||||
@ -178,7 +186,6 @@ class GenerateImage extends Operation {
|
|||||||
|
|
||||||
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default GenerateImage;
|
export default GenerateImage;
|
||||||
|
|||||||
@ -9,13 +9,12 @@ import OperationError from "../errors/OperationError.mjs";
|
|||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import Jimp from "jimp/es/index.js";
|
import { Jimp, JimpMime } from "jimp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image Brightness / Contrast operation
|
* Image Brightness / Contrast operation
|
||||||
*/
|
*/
|
||||||
class ImageBrightnessContrast extends Operation {
|
class ImageBrightnessContrast extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ImageBrightnessContrast constructor
|
* ImageBrightnessContrast constructor
|
||||||
*/
|
*/
|
||||||
@ -35,15 +34,15 @@ class ImageBrightnessContrast extends Operation {
|
|||||||
type: "number",
|
type: "number",
|
||||||
value: 0,
|
value: 0,
|
||||||
min: -100,
|
min: -100,
|
||||||
max: 100
|
max: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Contrast",
|
name: "Contrast",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 0,
|
value: 0,
|
||||||
min: -100,
|
min: -100,
|
||||||
max: 100
|
max: 100,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,14 +76,16 @@ class ImageBrightnessContrast extends Operation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.mime === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
imageBuffer = await image.getBuffer(JimpMime.png);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
imageBuffer = await image.getBuffer(image.mime);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error adjusting image brightness or contrast. (${err})`);
|
throw new OperationError(
|
||||||
|
`Error adjusting image brightness or contrast. (${err})`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +105,6 @@ class ImageBrightnessContrast extends Operation {
|
|||||||
|
|
||||||
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ImageBrightnessContrast;
|
export default ImageBrightnessContrast;
|
||||||
|
|||||||
@ -9,13 +9,12 @@ import OperationError from "../errors/OperationError.mjs";
|
|||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import Jimp from "jimp/es/index.js";
|
import { Jimp, JimpMime } from "jimp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image Filter operation
|
* Image Filter operation
|
||||||
*/
|
*/
|
||||||
class ImageFilter extends Operation {
|
class ImageFilter extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ImageFilter constructor
|
* ImageFilter constructor
|
||||||
*/
|
*/
|
||||||
@ -33,11 +32,8 @@ class ImageFilter extends Operation {
|
|||||||
{
|
{
|
||||||
name: "Filter type",
|
name: "Filter type",
|
||||||
type: "option",
|
type: "option",
|
||||||
value: [
|
value: ["Greyscale", "Sepia"],
|
||||||
"Greyscale",
|
},
|
||||||
"Sepia"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +56,11 @@ class ImageFilter extends Operation {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (isWorkerEnvironment())
|
if (isWorkerEnvironment())
|
||||||
self.sendStatusMessage("Applying " + filterType.toLowerCase() + " filter to image...");
|
self.sendStatusMessage(
|
||||||
|
"Applying " +
|
||||||
|
filterType.toLowerCase() +
|
||||||
|
" filter to image...",
|
||||||
|
);
|
||||||
if (filterType === "Greyscale") {
|
if (filterType === "Greyscale") {
|
||||||
image.greyscale();
|
image.greyscale();
|
||||||
} else {
|
} else {
|
||||||
@ -68,14 +68,16 @@ class ImageFilter extends Operation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.mime === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
imageBuffer = await image.getBuffer(JimpMime.png);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
imageBuffer = await image.getBuffer(image.mime);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error applying filter to image. (${err})`);
|
throw new OperationError(
|
||||||
|
`Error applying filter to image. (${err})`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +97,6 @@ class ImageFilter extends Operation {
|
|||||||
|
|
||||||
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ImageFilter;
|
export default ImageFilter;
|
||||||
|
|||||||
@ -9,13 +9,12 @@ import OperationError from "../errors/OperationError.mjs";
|
|||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import Jimp from "jimp/es/index.js";
|
import { Jimp, JimpMime } from "jimp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image Hue/Saturation/Lightness operation
|
* Image Hue/Saturation/Lightness operation
|
||||||
*/
|
*/
|
||||||
class ImageHueSaturationLightness extends Operation {
|
class ImageHueSaturationLightness extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ImageHueSaturationLightness constructor
|
* ImageHueSaturationLightness constructor
|
||||||
*/
|
*/
|
||||||
@ -24,7 +23,8 @@ class ImageHueSaturationLightness extends Operation {
|
|||||||
|
|
||||||
this.name = "Image Hue/Saturation/Lightness";
|
this.name = "Image Hue/Saturation/Lightness";
|
||||||
this.module = "Image";
|
this.module = "Image";
|
||||||
this.description = "Adjusts the hue / saturation / lightness (HSL) values of an image.";
|
this.description =
|
||||||
|
"Adjusts the hue / saturation / lightness (HSL) values of an image.";
|
||||||
this.infoURL = "";
|
this.infoURL = "";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "ArrayBuffer";
|
this.outputType = "ArrayBuffer";
|
||||||
@ -35,22 +35,22 @@ class ImageHueSaturationLightness extends Operation {
|
|||||||
type: "number",
|
type: "number",
|
||||||
value: 0,
|
value: 0,
|
||||||
min: -360,
|
min: -360,
|
||||||
max: 360
|
max: 360,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Saturation",
|
name: "Saturation",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 0,
|
value: 0,
|
||||||
min: -100,
|
min: -100,
|
||||||
max: 100
|
max: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Lightness",
|
name: "Lightness",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 0,
|
value: 0,
|
||||||
min: -100,
|
min: -100,
|
||||||
max: 100
|
max: 100,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,43 +76,45 @@ class ImageHueSaturationLightness extends Operation {
|
|||||||
if (hue !== 0) {
|
if (hue !== 0) {
|
||||||
if (isWorkerEnvironment())
|
if (isWorkerEnvironment())
|
||||||
self.sendStatusMessage("Changing image hue...");
|
self.sendStatusMessage("Changing image hue...");
|
||||||
image.colour([
|
image.color([
|
||||||
{
|
{
|
||||||
apply: "hue",
|
apply: "hue",
|
||||||
params: [hue]
|
params: [hue],
|
||||||
}
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
if (saturation !== 0) {
|
if (saturation !== 0) {
|
||||||
if (isWorkerEnvironment())
|
if (isWorkerEnvironment())
|
||||||
self.sendStatusMessage("Changing image saturation...");
|
self.sendStatusMessage("Changing image saturation...");
|
||||||
image.colour([
|
image.color([
|
||||||
{
|
{
|
||||||
apply: "saturate",
|
apply: "saturate",
|
||||||
params: [saturation]
|
params: [saturation],
|
||||||
}
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
if (lightness !== 0) {
|
if (lightness !== 0) {
|
||||||
if (isWorkerEnvironment())
|
if (isWorkerEnvironment())
|
||||||
self.sendStatusMessage("Changing image lightness...");
|
self.sendStatusMessage("Changing image lightness...");
|
||||||
image.colour([
|
image.color([
|
||||||
{
|
{
|
||||||
apply: "lighten",
|
apply: "lighten",
|
||||||
params: [lightness]
|
params: [lightness],
|
||||||
}
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.mime === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
imageBuffer = await image.getBuffer(JimpMime.png);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
imageBuffer = await image.getBuffer(image.mime);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error adjusting image hue / saturation / lightness. (${err})`);
|
throw new OperationError(
|
||||||
|
`Error adjusting image hue / saturation / lightness. (${err})`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,13 +9,12 @@ import OperationError from "../errors/OperationError.mjs";
|
|||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import Jimp from "jimp/es/index.js";
|
import { Jimp, JimpMime } from "jimp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image Opacity operation
|
* Image Opacity operation
|
||||||
*/
|
*/
|
||||||
class ImageOpacity extends Operation {
|
class ImageOpacity extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ImageOpacity constructor
|
* ImageOpacity constructor
|
||||||
*/
|
*/
|
||||||
@ -35,8 +34,8 @@ class ImageOpacity extends Operation {
|
|||||||
type: "number",
|
type: "number",
|
||||||
value: 100,
|
value: 100,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 100
|
max: 100,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,10 +62,10 @@ class ImageOpacity extends Operation {
|
|||||||
image.opacity(opacity / 100);
|
image.opacity(opacity / 100);
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.mime === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
imageBuffer = await image.getBuffer(JimpMime.png);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
imageBuffer = await image.getBuffer(image.mime);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -90,7 +89,6 @@ class ImageOpacity extends Operation {
|
|||||||
|
|
||||||
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ImageOpacity;
|
export default ImageOpacity;
|
||||||
|
|||||||
@ -9,13 +9,12 @@ import OperationError from "../errors/OperationError.mjs";
|
|||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import Jimp from "jimp/es/index.js";
|
import { Jimp, JimpMime } from "jimp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invert Image operation
|
* Invert Image operation
|
||||||
*/
|
*/
|
||||||
class InvertImage extends Operation {
|
class InvertImage extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* InvertImage constructor
|
* InvertImage constructor
|
||||||
*/
|
*/
|
||||||
@ -54,10 +53,10 @@ class InvertImage extends Operation {
|
|||||||
image.invert();
|
image.invert();
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.mime === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
imageBuffer = await image.getBuffer(JimpMime.png);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
imageBuffer = await image.getBuffer(image.mime);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -81,7 +80,6 @@ class InvertImage extends Operation {
|
|||||||
|
|
||||||
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default InvertImage;
|
export default InvertImage;
|
||||||
|
|||||||
@ -30,7 +30,12 @@ class Jq extends Operation {
|
|||||||
name: "Query",
|
name: "Query",
|
||||||
type: "string",
|
type: "string",
|
||||||
value: ""
|
value: ""
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
name: "Raw",
|
||||||
|
type: "boolean",
|
||||||
|
value: false
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,7 +45,7 @@ class Jq extends Operation {
|
|||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const [query] = args;
|
const [query, raw] = args;
|
||||||
let result;
|
let result;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -48,8 +53,11 @@ class Jq extends Operation {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Invalid jq expression: ${err.message}`);
|
throw new OperationError(`Invalid jq expression: ${err.message}`);
|
||||||
}
|
}
|
||||||
|
if (raw && typeof result === "string") {
|
||||||
return JSON.stringify(result);
|
return result;
|
||||||
|
} else {
|
||||||
|
return JSON.stringify(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,13 +8,12 @@ import Operation from "../Operation.mjs";
|
|||||||
import OperationError from "../errors/OperationError.mjs";
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import Jimp from "jimp/es/index.js";
|
import { Jimp, JimpMime } from "jimp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalise Image operation
|
* Normalise Image operation
|
||||||
*/
|
*/
|
||||||
class NormaliseImage extends Operation {
|
class NormaliseImage extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NormaliseImage constructor
|
* NormaliseImage constructor
|
||||||
*/
|
*/
|
||||||
@ -27,7 +26,7 @@ class NormaliseImage extends Operation {
|
|||||||
this.infoURL = "";
|
this.infoURL = "";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "ArrayBuffer";
|
this.outputType = "ArrayBuffer";
|
||||||
this.presentType= "html";
|
this.presentType = "html";
|
||||||
this.args = [];
|
this.args = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,10 +51,10 @@ class NormaliseImage extends Operation {
|
|||||||
image.normalize();
|
image.normalize();
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.mime === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
imageBuffer = await image.getBuffer(JimpMime.png);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
imageBuffer = await image.getBuffer(image.mime);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -79,7 +78,6 @@ class NormaliseImage extends Operation {
|
|||||||
|
|
||||||
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NormaliseImage;
|
export default NormaliseImage;
|
||||||
|
|||||||
@ -99,7 +99,7 @@ class OffsetChecker extends Operation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return outputs.join(sampleDelim);
|
return outputs.join(Utils.escapeHtml(sampleDelim));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
112
src/core/operations/ParseEthernetFrame.mjs
Normal file
112
src/core/operations/ParseEthernetFrame.mjs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* @author tedk [tedk@ted.do]
|
||||||
|
* @copyright Crown Copyright 2024
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import {fromHex, toHex} from "../lib/Hex.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse Ethernet frame operation
|
||||||
|
*/
|
||||||
|
class ParseEthernetFrame extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ParseEthernetFrame constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Parse Ethernet frame";
|
||||||
|
this.module = "Default";
|
||||||
|
this.description = "Parses an Ethernet frame and either shows the deduced values (Source and destination MAC, VLANs) or returns the packet data.<br /><br />Good for use in conjunction with the Parse IPv4, and Parse TCP/UDP recipes.";
|
||||||
|
this.infoURL = "https://en.wikipedia.org/wiki/Ethernet_frame#Frame_%E2%80%93_data_link_layer";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "html";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
name: "Input type",
|
||||||
|
type: "option",
|
||||||
|
value: [
|
||||||
|
"Raw", "Hex"
|
||||||
|
],
|
||||||
|
defaultIndex: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Return type",
|
||||||
|
type: "option",
|
||||||
|
value: [
|
||||||
|
"Text output", "Packet data", "Packet data (hex)",
|
||||||
|
],
|
||||||
|
defaultIndex: 0,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {html}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const format = args[0];
|
||||||
|
const outputFormat = args[1];
|
||||||
|
|
||||||
|
if (format === "Hex") {
|
||||||
|
input = fromHex(input);
|
||||||
|
} else if (format === "Raw") {
|
||||||
|
input = new Uint8Array(Utils.strToArrayBuffer(input));
|
||||||
|
} else {
|
||||||
|
throw new OperationError("Invalid input format selected.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const destinationMac = input.slice(0, 6);
|
||||||
|
const sourceMac = input.slice(6, 12);
|
||||||
|
|
||||||
|
let offset = 12;
|
||||||
|
const vlans = [];
|
||||||
|
|
||||||
|
while (offset < input.length) {
|
||||||
|
const ethType = Utils.byteArrayToChars(input.slice(offset, offset+2));
|
||||||
|
offset += 2;
|
||||||
|
|
||||||
|
if (ethType === "\x81\x00" || ethType === "\x88\xA8") {
|
||||||
|
// Parse the VLAN tag:
|
||||||
|
// [0000] 0000 0000 0000
|
||||||
|
// ^^^ PRIO - Ignored
|
||||||
|
// ^ DEI - Ignored
|
||||||
|
// ^^^^ ^^^^ ^^^^ VLAN ID
|
||||||
|
const vlanTag = input.slice(offset, offset+2);
|
||||||
|
vlans.push(((vlanTag[0] & 0b00001111) << 8) | vlanTag[1]);
|
||||||
|
|
||||||
|
offset += 2;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const packetData = input.slice(offset);
|
||||||
|
|
||||||
|
if (outputFormat === "Packet data") {
|
||||||
|
return Utils.byteArrayToChars(packetData);
|
||||||
|
} else if (outputFormat === "Packet data (hex)") {
|
||||||
|
return toHex(packetData);
|
||||||
|
} else if (outputFormat === "Text output") {
|
||||||
|
let retval = `Source MAC: ${toHex(sourceMac, ":")}\nDestination MAC: ${toHex(destinationMac, ":")}\n`;
|
||||||
|
if (vlans.length > 0) {
|
||||||
|
retval += `VLAN: ${vlans.join(", ")}\n`;
|
||||||
|
}
|
||||||
|
retval += `Data:\n${toHex(packetData)}`;
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ParseEthernetFrame;
|
||||||
@ -33,6 +33,12 @@ class ParseIPv4Header extends Operation {
|
|||||||
"name": "Input format",
|
"name": "Input format",
|
||||||
"type": "option",
|
"type": "option",
|
||||||
"value": ["Hex", "Raw"]
|
"value": ["Hex", "Raw"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Output format",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["Table", "Data (hex)", "Data (raw)"],
|
||||||
|
defaultIndex: 0,
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -44,6 +50,8 @@ class ParseIPv4Header extends Operation {
|
|||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const format = args[0];
|
const format = args[0];
|
||||||
|
const outputFormat = args[1];
|
||||||
|
|
||||||
let output;
|
let output;
|
||||||
|
|
||||||
if (format === "Hex") {
|
if (format === "Hex") {
|
||||||
@ -98,7 +106,10 @@ class ParseIPv4Header extends Operation {
|
|||||||
checksumResult = givenChecksum + " (incorrect, should be " + correctChecksum + ")";
|
checksumResult = givenChecksum + " (incorrect, should be " + correctChecksum + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
output = `<table class='table table-hover table-sm table-bordered table-nonfluid'><tr><th>Field</th><th>Value</th></tr>
|
const data = input.slice(ihl * 4);
|
||||||
|
|
||||||
|
if (outputFormat === "Table") {
|
||||||
|
output = `<table class='table table-hover table-sm table-bordered table-nonfluid'><tr><th>Field</th><th>Value</th></tr>
|
||||||
<tr><td>Version</td><td>${version}</td></tr>
|
<tr><td>Version</td><td>${version}</td></tr>
|
||||||
<tr><td>Internet Header Length (IHL)</td><td>${ihl} (${ihl * 4} bytes)</td></tr>
|
<tr><td>Internet Header Length (IHL)</td><td>${ihl} (${ihl * 4} bytes)</td></tr>
|
||||||
<tr><td>Differentiated Services Code Point (DSCP)</td><td>${dscp}</td></tr>
|
<tr><td>Differentiated Services Code Point (DSCP)</td><td>${dscp}</td></tr>
|
||||||
@ -116,13 +127,19 @@ class ParseIPv4Header extends Operation {
|
|||||||
<tr><td>Protocol</td><td>${protocol}, ${protocolInfo.protocol} (${protocolInfo.keyword})</td></tr>
|
<tr><td>Protocol</td><td>${protocol}, ${protocolInfo.protocol} (${protocolInfo.keyword})</td></tr>
|
||||||
<tr><td>Header checksum</td><td>${checksumResult}</td></tr>
|
<tr><td>Header checksum</td><td>${checksumResult}</td></tr>
|
||||||
<tr><td>Source IP address</td><td>${ipv4ToStr(srcIP)}</td></tr>
|
<tr><td>Source IP address</td><td>${ipv4ToStr(srcIP)}</td></tr>
|
||||||
<tr><td>Destination IP address</td><td>${ipv4ToStr(dstIP)}</td></tr>`;
|
<tr><td>Destination IP address</td><td>${ipv4ToStr(dstIP)}</td></tr>
|
||||||
|
<tr><td>Data (hex)</td><td>${toHex(data)}</td></tr>`;
|
||||||
|
|
||||||
if (ihl > 5) {
|
if (ihl > 5) {
|
||||||
output += `<tr><td>Options</td><td>${toHex(options)}</td></tr>`;
|
output += `<tr><td>Options</td><td>${toHex(options)}</td></tr>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return output + "</table>";
|
||||||
|
} else if (outputFormat === "Data (hex)") {
|
||||||
|
return toHex(data);
|
||||||
|
} else if (outputFormat === "Data (raw)") {
|
||||||
|
return Utils.byteArrayToChars(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
return output + "</table>";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,6 @@ import { parseQrCode } from "../lib/QRCode.mjs";
|
|||||||
* Parse QR Code operation
|
* Parse QR Code operation
|
||||||
*/
|
*/
|
||||||
class ParseQRCode extends Operation {
|
class ParseQRCode extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ParseQRCode constructor
|
* ParseQRCode constructor
|
||||||
*/
|
*/
|
||||||
@ -22,24 +21,26 @@ class ParseQRCode extends Operation {
|
|||||||
|
|
||||||
this.name = "Parse QR Code";
|
this.name = "Parse QR Code";
|
||||||
this.module = "Image";
|
this.module = "Image";
|
||||||
this.description = "Reads an image file and attempts to detect and read a Quick Response (QR) code from the image.<br><br><u>Normalise Image</u><br>Attempts to normalise the image before parsing it to improve detection of a QR code.";
|
this.description =
|
||||||
|
"Reads an image file and attempts to detect and read a Quick Response (QR) code from the image.<br><br><u>Normalise Image</u><br>Attempts to normalise the image before parsing it to improve detection of a QR code.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/QR_code";
|
this.infoURL = "https://wikipedia.org/wiki/QR_code";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "string";
|
this.outputType = "string";
|
||||||
this.args = [
|
this.args = [
|
||||||
{
|
{
|
||||||
"name": "Normalise image",
|
name: "Normalise image",
|
||||||
"type": "boolean",
|
type: "boolean",
|
||||||
"value": false
|
value: false,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
this.checks = [
|
this.checks = [
|
||||||
{
|
{
|
||||||
"pattern": "^(?:\\xff\\xd8\\xff|\\x89\\x50\\x4e\\x47|\\x47\\x49\\x46|.{8}\\x57\\x45\\x42\\x50|\\x42\\x4d)",
|
pattern:
|
||||||
"flags": "",
|
"^(?:\\xff\\xd8\\xff|\\x89\\x50\\x4e\\x47|\\x47\\x49\\x46|.{8}\\x57\\x45\\x42\\x50|\\x42\\x4d)",
|
||||||
"args": [false],
|
flags: "",
|
||||||
"useful": true
|
args: [false],
|
||||||
}
|
useful: true,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,9 +55,8 @@ class ParseQRCode extends Operation {
|
|||||||
if (!isImage(input)) {
|
if (!isImage(input)) {
|
||||||
throw new OperationError("Invalid file type.");
|
throw new OperationError("Invalid file type.");
|
||||||
}
|
}
|
||||||
return await parseQrCode(input, normalise);
|
return parseQrCode(input, normalise);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ParseQRCode;
|
export default ParseQRCode;
|
||||||
|
|||||||
164
src/core/operations/PseudoRandomIntegerGenerator.mjs
Normal file
164
src/core/operations/PseudoRandomIntegerGenerator.mjs
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
/**
|
||||||
|
* @author cktgh [chankaitung@gmail.com]
|
||||||
|
* @copyright Crown Copyright 2026
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import forge from "node-forge";
|
||||||
|
import Utils, { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
|
import { DELIM_OPTIONS } from "../lib/Delim.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pseudo-Random Integer Generator operation
|
||||||
|
*/
|
||||||
|
class PseudoRandomIntegerGenerator extends Operation {
|
||||||
|
|
||||||
|
// in theory 2**53 is the max range, but we use Number.MAX_SAFE_INTEGER (2**53 - 1) as it is more consistent.
|
||||||
|
static MAX_RANGE = Number.MAX_SAFE_INTEGER;
|
||||||
|
// arbitrary choice
|
||||||
|
static BUFFER_SIZE = 1024;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PseudoRandomIntegerGenerator constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Pseudo-Random Integer Generator";
|
||||||
|
this.module = "Ciphers";
|
||||||
|
this.description = "A cryptographically-secure pseudo-random number generator (PRNG).<br><br>Generates random integers within a specified range using the browser's built-in <code>crypto.getRandomValues()</code> method if available.<br><br>The supported range of integers is from <code>-(2^53 - 1)</code> to <code>(2^53 - 1)</code>.";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/Pseudorandom_number_generator";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Number of Integers",
|
||||||
|
"type": "number",
|
||||||
|
"value": 1,
|
||||||
|
"min": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Min Value",
|
||||||
|
"type": "number",
|
||||||
|
"value": 0,
|
||||||
|
"min": Number.MIN_SAFE_INTEGER,
|
||||||
|
"max": Number.MAX_SAFE_INTEGER
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Max Value",
|
||||||
|
"type": "number",
|
||||||
|
"value": 99,
|
||||||
|
"min": Number.MIN_SAFE_INTEGER,
|
||||||
|
"max": Number.MAX_SAFE_INTEGER
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Delimiter",
|
||||||
|
"type": "option",
|
||||||
|
"value": DELIM_OPTIONS
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Output",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["Raw", "Hex", "Decimal"]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// not using BigUint64Array to avoid BigInt handling overhead
|
||||||
|
this.randomBuffer = new Uint32Array(PseudoRandomIntegerGenerator.BUFFER_SIZE);
|
||||||
|
this.randomBufferOffset = PseudoRandomIntegerGenerator.BUFFER_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const [numInts, minInt, maxInt, delimiter, outputType] = args;
|
||||||
|
|
||||||
|
if (minInt === null || maxInt === null) return "";
|
||||||
|
|
||||||
|
const min = Math.ceil(minInt);
|
||||||
|
const max = Math.floor(maxInt);
|
||||||
|
const delim = Utils.charRep(delimiter || "Space");
|
||||||
|
|
||||||
|
if (!Number.isSafeInteger(min) || !Number.isSafeInteger(max)) {
|
||||||
|
throw new OperationError("Min and Max must be between `-(2^53 - 1)` and `2^53 - 1`.");
|
||||||
|
}
|
||||||
|
if (min > max) {
|
||||||
|
throw new OperationError("Min cannot be larger than Max.");
|
||||||
|
}
|
||||||
|
const range = max - min + 1; // inclusive range
|
||||||
|
if (range > PseudoRandomIntegerGenerator.MAX_RANGE) {
|
||||||
|
throw new OperationError("Range between Min and Max cannot be larger than `2^53`");
|
||||||
|
}
|
||||||
|
|
||||||
|
// as large as possible while divisible by range
|
||||||
|
const rejectionThreshold = PseudoRandomIntegerGenerator.MAX_RANGE - (PseudoRandomIntegerGenerator.MAX_RANGE % range);
|
||||||
|
const output = [];
|
||||||
|
for (let i = 0; i < numInts; i++) {
|
||||||
|
const result = this._generateRandomValue(rejectionThreshold);
|
||||||
|
const intValue = min + (result % range);
|
||||||
|
|
||||||
|
switch (outputType) {
|
||||||
|
case "Hex":
|
||||||
|
output.push(intValue.toString(16));
|
||||||
|
break;
|
||||||
|
case "Decimal":
|
||||||
|
output.push(intValue.toString(10));
|
||||||
|
break;
|
||||||
|
case "Raw":
|
||||||
|
default:
|
||||||
|
output.push(Utils.chr(intValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outputType === "Raw") {
|
||||||
|
return output.join("");
|
||||||
|
}
|
||||||
|
return output.join(delim);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a random value, result will be less than the rejection threshold (exclusive).
|
||||||
|
*
|
||||||
|
* @param {number} rejectionThreshold
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
_generateRandomValue(rejectionThreshold) {
|
||||||
|
let result;
|
||||||
|
do {
|
||||||
|
if (this.randomBufferOffset + 2 > this.randomBuffer.length) {
|
||||||
|
this._resetRandomBuffer();
|
||||||
|
}
|
||||||
|
// stitching a 53 bit number; not using BigUint64Array to avoid BigInt handling overhead
|
||||||
|
result = (this.randomBuffer[this.randomBufferOffset++] & 0x1f_ffff) * 0x1_0000_0000 +
|
||||||
|
this.randomBuffer[this.randomBufferOffset++];
|
||||||
|
} while (result >= rejectionThreshold);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill random buffer with new random values and rseet the offset.
|
||||||
|
*/
|
||||||
|
_resetRandomBuffer() {
|
||||||
|
if (isWorkerEnvironment() && self.crypto) {
|
||||||
|
self.crypto.getRandomValues(this.randomBuffer);
|
||||||
|
} else {
|
||||||
|
const bytes = forge.random.getBytesSync(this.randomBuffer.length * 4);
|
||||||
|
for (let j = 0; j < this.randomBuffer.length; j++) {
|
||||||
|
this.randomBuffer[j] = (bytes.charCodeAt(j * 4) << 24) |
|
||||||
|
(bytes.charCodeAt(j * 4 + 1) << 16) |
|
||||||
|
(bytes.charCodeAt(j * 4 + 2) << 8) |
|
||||||
|
bytes.charCodeAt(j * 4 + 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.randomBufferOffset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PseudoRandomIntegerGenerator;
|
||||||
119
src/core/operations/RC6Decrypt.mjs
Normal file
119
src/core/operations/RC6Decrypt.mjs
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
/**
|
||||||
|
* @author Medjedtxm
|
||||||
|
* @copyright Crown Copyright 2026
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import { toHex } from "../lib/Hex.mjs";
|
||||||
|
import { decryptRC6, getBlockSize, getDefaultRounds } from "../lib/RC6.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RC6 Decrypt operation
|
||||||
|
*/
|
||||||
|
class RC6Decrypt extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RC6Decrypt constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "RC6 Decrypt";
|
||||||
|
this.module = "Ciphers";
|
||||||
|
this.description = "RC6 is a symmetric key block cipher derived from RC5. It was designed by Ron Rivest, Matt Robshaw, Ray Sidney, and Yiqun Lisa Yin to meet the requirements of the AES competition, and was one of the five finalists.<br><br>RC6 is parameterised as RC6-w/r/b where w is word size in bits (any multiple of 8 from 8-256), r is the number of rounds (1-255), and b is the key length in bytes. The standard AES submission uses w=32, r=20. Common word sizes: 8, 16, 32 (standard), 64, 128.<br><br><b>IV:</b> The Initialisation Vector should be 4*w/8 bytes (e.g. 16 bytes for w=32). If not entered, it will default to null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, the PKCS#7 padding scheme is used.";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/RC6";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Key",
|
||||||
|
"type": "toggleString",
|
||||||
|
"value": "",
|
||||||
|
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "IV",
|
||||||
|
"type": "toggleString",
|
||||||
|
"value": "",
|
||||||
|
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mode",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["CBC", "CFB", "OFB", "CTR", "ECB"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Input",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["Hex", "Raw"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Output",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["Raw", "Hex"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Padding",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["PKCS5", "NO", "ZERO", "RANDOM", "BIT"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Word Size",
|
||||||
|
"type": "number",
|
||||||
|
"value": 32,
|
||||||
|
"min": 8,
|
||||||
|
"max": 256,
|
||||||
|
"step": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Rounds",
|
||||||
|
"type": "number",
|
||||||
|
"value": 20,
|
||||||
|
"min": 1,
|
||||||
|
"max": 255
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const key = Utils.convertToByteArray(args[0].string, args[0].option),
|
||||||
|
iv = Utils.convertToByteArray(args[1].string, args[1].option),
|
||||||
|
[,, mode, inputType, outputType, padding, wordSize, rounds] = args;
|
||||||
|
|
||||||
|
// Validate word size
|
||||||
|
if (!Number.isInteger(wordSize) || wordSize < 8 || wordSize > 256 || wordSize % 8 !== 0)
|
||||||
|
throw new OperationError(`Invalid word size: ${wordSize}. Must be a multiple of 8 between 8 and 256.`);
|
||||||
|
|
||||||
|
const blockSize = getBlockSize(wordSize);
|
||||||
|
const defaultRounds = getDefaultRounds(wordSize);
|
||||||
|
|
||||||
|
if (iv.length !== blockSize && iv.length !== 0 && mode !== "ECB")
|
||||||
|
throw new OperationError(`Invalid IV length: ${iv.length} bytes
|
||||||
|
|
||||||
|
RC6-${wordSize} uses an IV length of ${blockSize} bytes (${blockSize * 8} bits).
|
||||||
|
Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
|
||||||
|
|
||||||
|
if (!Number.isInteger(rounds) || rounds < 1 || rounds > 255)
|
||||||
|
throw new OperationError(`Invalid number of rounds: ${rounds}
|
||||||
|
|
||||||
|
Rounds must be an integer between 1 and 255. Standard for w=${wordSize} is ${defaultRounds}.`);
|
||||||
|
|
||||||
|
// Default IV to null bytes if empty (like AES)
|
||||||
|
const actualIv = iv.length === 0 ? new Array(blockSize).fill(0) : iv;
|
||||||
|
|
||||||
|
input = Utils.convertToByteArray(input, inputType);
|
||||||
|
const output = decryptRC6(input, key, actualIv, mode, padding, rounds, wordSize);
|
||||||
|
return outputType === "Hex" ? toHex(output, "") : Utils.byteArrayToUtf8(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RC6Decrypt;
|
||||||
119
src/core/operations/RC6Encrypt.mjs
Normal file
119
src/core/operations/RC6Encrypt.mjs
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
/**
|
||||||
|
* @author Medjedtxm
|
||||||
|
* @copyright Crown Copyright 2026
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import { toHex } from "../lib/Hex.mjs";
|
||||||
|
import { encryptRC6, getBlockSize, getDefaultRounds } from "../lib/RC6.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RC6 Encrypt operation
|
||||||
|
*/
|
||||||
|
class RC6Encrypt extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RC6Encrypt constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "RC6 Encrypt";
|
||||||
|
this.module = "Ciphers";
|
||||||
|
this.description = "RC6 is a symmetric key block cipher derived from RC5. It was designed by Ron Rivest, Matt Robshaw, Ray Sidney, and Yiqun Lisa Yin to meet the requirements of the AES competition, and was one of the five finalists.<br><br>RC6 is parameterised as RC6-w/r/b where w is word size in bits (any multiple of 8 from 8-256), r is the number of rounds (1-255), and b is the key length in bytes. The standard AES submission uses w=32, r=20. Common word sizes: 8, 16, 32 (standard), 64, 128.<br><br><b>IV:</b> The Initialisation Vector should be 4*w/8 bytes (e.g. 16 bytes for w=32). If not entered, it will default to null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, the PKCS#7 padding scheme is used.";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/RC6";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Key",
|
||||||
|
"type": "toggleString",
|
||||||
|
"value": "",
|
||||||
|
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "IV",
|
||||||
|
"type": "toggleString",
|
||||||
|
"value": "",
|
||||||
|
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mode",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["CBC", "CFB", "OFB", "CTR", "ECB"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Input",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["Raw", "Hex"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Output",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["Hex", "Raw"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Padding",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["PKCS5", "NO", "ZERO", "RANDOM", "BIT"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Word Size",
|
||||||
|
"type": "number",
|
||||||
|
"value": 32,
|
||||||
|
"min": 8,
|
||||||
|
"max": 256,
|
||||||
|
"step": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Rounds",
|
||||||
|
"type": "number",
|
||||||
|
"value": 20,
|
||||||
|
"min": 1,
|
||||||
|
"max": 255
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const key = Utils.convertToByteArray(args[0].string, args[0].option),
|
||||||
|
iv = Utils.convertToByteArray(args[1].string, args[1].option),
|
||||||
|
[,, mode, inputType, outputType, padding, wordSize, rounds] = args;
|
||||||
|
|
||||||
|
// Validate word size
|
||||||
|
if (!Number.isInteger(wordSize) || wordSize < 8 || wordSize > 256 || wordSize % 8 !== 0)
|
||||||
|
throw new OperationError(`Invalid word size: ${wordSize}. Must be a multiple of 8 between 8 and 256.`);
|
||||||
|
|
||||||
|
const blockSize = getBlockSize(wordSize);
|
||||||
|
const defaultRounds = getDefaultRounds(wordSize);
|
||||||
|
|
||||||
|
if (iv.length !== blockSize && iv.length !== 0 && mode !== "ECB")
|
||||||
|
throw new OperationError(`Invalid IV length: ${iv.length} bytes
|
||||||
|
|
||||||
|
RC6-${wordSize} uses an IV length of ${blockSize} bytes (${blockSize * 8} bits).
|
||||||
|
Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
|
||||||
|
|
||||||
|
if (!Number.isInteger(rounds) || rounds < 1 || rounds > 255)
|
||||||
|
throw new OperationError(`Invalid number of rounds: ${rounds}
|
||||||
|
|
||||||
|
Rounds must be an integer between 1 and 255. Standard for w=${wordSize} is ${defaultRounds}.`);
|
||||||
|
|
||||||
|
// Default IV to null bytes if empty (like AES)
|
||||||
|
const actualIv = iv.length === 0 ? new Array(blockSize).fill(0) : iv;
|
||||||
|
|
||||||
|
input = Utils.convertToByteArray(input, inputType);
|
||||||
|
const output = encryptRC6(input, key, actualIv, mode, padding, rounds, wordSize);
|
||||||
|
return outputType === "Hex" ? toHex(output, "") : Utils.byteArrayToUtf8(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RC6Encrypt;
|
||||||
@ -10,13 +10,12 @@ import Utils from "../Utils.mjs";
|
|||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { runHash } from "../lib/Hash.mjs";
|
import { runHash } from "../lib/Hash.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import Jimp from "jimp/es/index.js";
|
import { Jimp } from "jimp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Randomize Colour Palette operation
|
* Randomize Colour Palette operation
|
||||||
*/
|
*/
|
||||||
class RandomizeColourPalette extends Operation {
|
class RandomizeColourPalette extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RandomizeColourPalette constructor
|
* RandomizeColourPalette constructor
|
||||||
*/
|
*/
|
||||||
@ -25,7 +24,8 @@ class RandomizeColourPalette extends Operation {
|
|||||||
|
|
||||||
this.name = "Randomize Colour Palette";
|
this.name = "Randomize Colour Palette";
|
||||||
this.module = "Image";
|
this.module = "Image";
|
||||||
this.description = "Randomizes each colour in an image's colour palette. This can often reveal text or symbols that were previously a very similar colour to their surroundings, a technique sometimes used in Steganography.";
|
this.description =
|
||||||
|
"Randomizes each colour in an image's colour palette. This can often reveal text or symbols that were previously a very similar colour to their surroundings, a technique sometimes used in Steganography.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Indexed_color";
|
this.infoURL = "https://wikipedia.org/wiki/Indexed_color";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "ArrayBuffer";
|
this.outputType = "ArrayBuffer";
|
||||||
@ -34,8 +34,8 @@ class RandomizeColourPalette extends Operation {
|
|||||||
{
|
{
|
||||||
name: "Seed",
|
name: "Seed",
|
||||||
type: "string",
|
type: "string",
|
||||||
value: ""
|
value: "",
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,23 +45,24 @@ class RandomizeColourPalette extends Operation {
|
|||||||
* @returns {ArrayBuffer}
|
* @returns {ArrayBuffer}
|
||||||
*/
|
*/
|
||||||
async run(input, args) {
|
async run(input, args) {
|
||||||
if (!isImage(input)) throw new OperationError("Please enter a valid image file.");
|
if (!isImage(input))
|
||||||
|
throw new OperationError("Please enter a valid image file.");
|
||||||
|
|
||||||
const seed = args[0] || (Math.random().toString().substr(2)),
|
const seed = args[0] || Math.random().toString().substr(2),
|
||||||
parsedImage = await Jimp.read(input),
|
parsedImage = await Jimp.read(input),
|
||||||
width = parsedImage.bitmap.width,
|
width = parsedImage.bitmap.width,
|
||||||
height = parsedImage.bitmap.height;
|
height = parsedImage.bitmap.height;
|
||||||
|
|
||||||
let rgbString, rgbHash, rgbHex;
|
let rgbString, rgbHash, rgbHex;
|
||||||
|
|
||||||
parsedImage.scan(0, 0, width, height, function(x, y, idx) {
|
parsedImage.scan(0, 0, width, height, function (x, y, idx) {
|
||||||
rgbString = this.bitmap.data.slice(idx, idx+3).join(".");
|
rgbString = this.bitmap.data.slice(idx, idx + 3).join(".");
|
||||||
rgbHash = runHash("md5", Utils.strToArrayBuffer(seed + rgbString));
|
rgbHash = runHash("md5", Utils.strToArrayBuffer(seed + rgbString));
|
||||||
rgbHex = rgbHash.substr(0, 6) + "ff";
|
rgbHex = rgbHash.substr(0, 6) + "ff";
|
||||||
parsedImage.setPixelColor(parseInt(rgbHex, 16), x, y);
|
parsedImage.setPixelColor(parseInt(rgbHex, 16), x, y);
|
||||||
});
|
});
|
||||||
|
|
||||||
const imageBuffer = await parsedImage.getBufferAsync(Jimp.AUTO);
|
const imageBuffer = await parsedImage.getBuffer(parsedImage.mime);
|
||||||
|
|
||||||
return new Uint8Array(imageBuffer).buffer;
|
return new Uint8Array(imageBuffer).buffer;
|
||||||
}
|
}
|
||||||
@ -77,7 +78,6 @@ class RandomizeColourPalette extends Operation {
|
|||||||
|
|
||||||
return `<img src="data:${type};base64,${toBase64(data)}">`;
|
return `<img src="data:${type};base64,${toBase64(data)}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RandomizeColourPalette;
|
export default RandomizeColourPalette;
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
import XRegExp from "xregexp";
|
import XRegExp from "xregexp";
|
||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.mjs";
|
||||||
import Utils from "../Utils.mjs";
|
import Utils from "../Utils.mjs";
|
||||||
|
import { EMAIL_REGEX } from "../lib/Extract.mjs";
|
||||||
import OperationError from "../errors/OperationError.mjs";
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,7 +46,7 @@ class RegularExpression extends Operation {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Email address",
|
name: "Email address",
|
||||||
value: "(?:[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9](?:[\\u00A0-\\uD7FF\\uE000-\\uFFFF-a-z0-9-]*[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9])?\\.)+[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9](?:[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9-]*[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9])?|\\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\\.){3}\\])"
|
value: EMAIL_REGEX.source // We use a different regex library, so just take the source regex string here
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "URL",
|
name: "URL",
|
||||||
|
|||||||
@ -9,13 +9,12 @@ import OperationError from "../errors/OperationError.mjs";
|
|||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import Jimp from "jimp/es/index.js";
|
import { Jimp, JimpMime, ResizeStrategy } from "jimp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resize Image operation
|
* Resize Image operation
|
||||||
*/
|
*/
|
||||||
class ResizeImage extends Operation {
|
class ResizeImage extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ResizeImage constructor
|
* ResizeImage constructor
|
||||||
*/
|
*/
|
||||||
@ -24,7 +23,8 @@ class ResizeImage extends Operation {
|
|||||||
|
|
||||||
this.name = "Resize Image";
|
this.name = "Resize Image";
|
||||||
this.module = "Image";
|
this.module = "Image";
|
||||||
this.description = "Resizes an image to the specified width and height values.";
|
this.description =
|
||||||
|
"Resizes an image to the specified width and height values.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Image_scaling";
|
this.infoURL = "https://wikipedia.org/wiki/Image_scaling";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "ArrayBuffer";
|
this.outputType = "ArrayBuffer";
|
||||||
@ -34,23 +34,23 @@ class ResizeImage extends Operation {
|
|||||||
name: "Width",
|
name: "Width",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 100,
|
value: 100,
|
||||||
min: 1
|
min: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Height",
|
name: "Height",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 100,
|
value: 100,
|
||||||
min: 1
|
min: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Unit type",
|
name: "Unit type",
|
||||||
type: "option",
|
type: "option",
|
||||||
value: ["Pixels", "Percent"]
|
value: ["Pixels", "Percent"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Maintain aspect ratio",
|
name: "Maintain aspect ratio",
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
value: false
|
value: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Resizing algorithm",
|
name: "Resizing algorithm",
|
||||||
@ -60,10 +60,10 @@ class ResizeImage extends Operation {
|
|||||||
"Bilinear",
|
"Bilinear",
|
||||||
"Bicubic",
|
"Bicubic",
|
||||||
"Hermite",
|
"Hermite",
|
||||||
"Bezier"
|
"Bezier",
|
||||||
],
|
],
|
||||||
defaultIndex: 1
|
defaultIndex: 1,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,11 +80,11 @@ class ResizeImage extends Operation {
|
|||||||
resizeAlg = args[4];
|
resizeAlg = args[4];
|
||||||
|
|
||||||
const resizeMap = {
|
const resizeMap = {
|
||||||
"Nearest Neighbour": Jimp.RESIZE_NEAREST_NEIGHBOR,
|
"Nearest Neighbour": ResizeStrategy.NEAREST_NEIGHBOR,
|
||||||
"Bilinear": Jimp.RESIZE_BILINEAR,
|
Bilinear: ResizeStrategy.BILINEAR,
|
||||||
"Bicubic": Jimp.RESIZE_BICUBIC,
|
Bicubic: ResizeStrategy.BICUBIC,
|
||||||
"Hermite": Jimp.RESIZE_HERMITE,
|
Hermite: ResizeStrategy.HERMITE,
|
||||||
"Bezier": Jimp.RESIZE_BEZIER
|
Bezier: ResizeStrategy.BEZIER,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isImage(input)) {
|
if (!isImage(input)) {
|
||||||
@ -99,23 +99,31 @@ class ResizeImage extends Operation {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (unit === "Percent") {
|
if (unit === "Percent") {
|
||||||
width = image.getWidth() * (width / 100);
|
width = image.width * (width / 100);
|
||||||
height = image.getHeight() * (height / 100);
|
height = image.height * (height / 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isWorkerEnvironment())
|
if (isWorkerEnvironment())
|
||||||
self.sendStatusMessage("Resizing image...");
|
self.sendStatusMessage("Resizing image...");
|
||||||
if (aspect) {
|
if (aspect) {
|
||||||
image.scaleToFit(width, height, resizeMap[resizeAlg]);
|
image.scaleToFit({
|
||||||
|
w: width,
|
||||||
|
h: height,
|
||||||
|
mode: resizeMap[resizeAlg],
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
image.resize(width, height, resizeMap[resizeAlg]);
|
image.resize({
|
||||||
|
w: width,
|
||||||
|
h: height,
|
||||||
|
mode: resizeMap[resizeAlg],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.mime === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
imageBuffer = await image.getBuffer(JimpMime.png);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
imageBuffer = await image.getBuffer(image.mime);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -139,7 +147,6 @@ class ResizeImage extends Operation {
|
|||||||
|
|
||||||
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ResizeImage;
|
export default ResizeImage;
|
||||||
|
|||||||
@ -9,13 +9,12 @@ import OperationError from "../errors/OperationError.mjs";
|
|||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import Jimp from "jimp/es/index.js";
|
import { Jimp, JimpMime } from "jimp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rotate Image operation
|
* Rotate Image operation
|
||||||
*/
|
*/
|
||||||
class RotateImage extends Operation {
|
class RotateImage extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RotateImage constructor
|
* RotateImage constructor
|
||||||
*/
|
*/
|
||||||
@ -24,7 +23,8 @@ class RotateImage extends Operation {
|
|||||||
|
|
||||||
this.name = "Rotate Image";
|
this.name = "Rotate Image";
|
||||||
this.module = "Image";
|
this.module = "Image";
|
||||||
this.description = "Rotates an image by the specified number of degrees.";
|
this.description =
|
||||||
|
"Rotates an image by the specified number of degrees.";
|
||||||
this.infoURL = "";
|
this.infoURL = "";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "ArrayBuffer";
|
this.outputType = "ArrayBuffer";
|
||||||
@ -33,8 +33,8 @@ class RotateImage extends Operation {
|
|||||||
{
|
{
|
||||||
name: "Rotation amount (degrees)",
|
name: "Rotation amount (degrees)",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 90
|
value: 90,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,10 +62,10 @@ class RotateImage extends Operation {
|
|||||||
image.rotate(degrees);
|
image.rotate(degrees);
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.mime === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
imageBuffer = await image.getBuffer(JimpMime.png);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
imageBuffer = await image.getBuffer(image.mime);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -89,7 +89,6 @@ class RotateImage extends Operation {
|
|||||||
|
|
||||||
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RotateImage;
|
export default RotateImage;
|
||||||
|
|||||||
@ -3,8 +3,7 @@
|
|||||||
* @copyright Crown Copyright 2016
|
* @copyright Crown Copyright 2016
|
||||||
* @license Apache-2.0
|
* @license Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
import { format } from "sql-formatter";
|
||||||
import vkbeautify from "vkbeautify";
|
|
||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,7 +38,26 @@ class SQLBeautify extends Operation {
|
|||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const indentStr = args[0];
|
const indentStr = args[0];
|
||||||
return vkbeautify.sql(input, indentStr);
|
// Extract and replace bind variables like :Bind1 with __BIND_0__
|
||||||
|
const bindRegex = /:\w+/g;
|
||||||
|
const bindMap = {};
|
||||||
|
let bindCounter=0;
|
||||||
|
const placeholderInput = input.replace(bindRegex, (match) => {
|
||||||
|
const placeholder = `__BIND_${bindCounter++}__`;
|
||||||
|
bindMap[placeholder] = match;
|
||||||
|
return placeholder;
|
||||||
|
});
|
||||||
|
// Format the SQL with chosen options
|
||||||
|
let formatted= format(placeholderInput, {
|
||||||
|
language: "mysql", // Use MySQL as the default dialect for better compatibility with real-world SQL
|
||||||
|
useTabs: indentStr==="\t", // true if tab, false if spaces
|
||||||
|
tabWidth: indentStr.length || 4, // fallback if empty
|
||||||
|
indentStyle: "standard" // fine for most SQL
|
||||||
|
});
|
||||||
|
// Replace placeholders back with original bind variables
|
||||||
|
formatted = formatted.replace(/__BIND_\d+__/g, match => bindMap[match] || match);
|
||||||
|
|
||||||
|
return formatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,15 +8,13 @@ import Operation from "../Operation.mjs";
|
|||||||
import OperationError from "../errors/OperationError.mjs";
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { gaussianBlur } from "../lib/ImageManipulation.mjs";
|
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import Jimp from "jimp/es/index.js";
|
import { Jimp, JimpMime } from "jimp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sharpen Image operation
|
* Sharpen Image operation
|
||||||
*/
|
*/
|
||||||
class SharpenImage extends Operation {
|
class SharpenImage extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SharpenImage constructor
|
* SharpenImage constructor
|
||||||
*/
|
*/
|
||||||
@ -35,22 +33,22 @@ class SharpenImage extends Operation {
|
|||||||
name: "Radius",
|
name: "Radius",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 2,
|
value: 2,
|
||||||
min: 1
|
min: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Amount",
|
name: "Amount",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 1,
|
value: 1,
|
||||||
min: 0,
|
min: 0,
|
||||||
step: 0.1
|
step: 0.1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Threshold",
|
name: "Threshold",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 10,
|
value: 10,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 100
|
max: 100,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,67 +77,102 @@ class SharpenImage extends Operation {
|
|||||||
const blurMask = image.clone();
|
const blurMask = image.clone();
|
||||||
|
|
||||||
if (isWorkerEnvironment())
|
if (isWorkerEnvironment())
|
||||||
self.sendStatusMessage("Sharpening image... (Blurring cloned image)");
|
self.sendStatusMessage(
|
||||||
const blurImage = gaussianBlur(image.clone(), radius);
|
"Sharpening image... (Blurring cloned image)",
|
||||||
|
);
|
||||||
|
const blurImage = image.clone().gaussian(radius);
|
||||||
|
|
||||||
if (isWorkerEnvironment())
|
if (isWorkerEnvironment())
|
||||||
self.sendStatusMessage("Sharpening image... (Creating unsharp mask)");
|
self.sendStatusMessage(
|
||||||
blurMask.scan(0, 0, blurMask.bitmap.width, blurMask.bitmap.height, function(x, y, idx) {
|
"Sharpening image... (Creating unsharp mask)",
|
||||||
const blurRed = blurImage.bitmap.data[idx];
|
);
|
||||||
const blurGreen = blurImage.bitmap.data[idx + 1];
|
blurMask.scan(
|
||||||
const blurBlue = blurImage.bitmap.data[idx + 2];
|
0,
|
||||||
|
0,
|
||||||
|
blurMask.bitmap.width,
|
||||||
|
blurMask.bitmap.height,
|
||||||
|
function (x, y, idx) {
|
||||||
|
const blurRed = blurImage.bitmap.data[idx];
|
||||||
|
const blurGreen = blurImage.bitmap.data[idx + 1];
|
||||||
|
const blurBlue = blurImage.bitmap.data[idx + 2];
|
||||||
|
|
||||||
const normalRed = this.bitmap.data[idx];
|
const normalRed = this.bitmap.data[idx];
|
||||||
const normalGreen = this.bitmap.data[idx + 1];
|
const normalGreen = this.bitmap.data[idx + 1];
|
||||||
const normalBlue = this.bitmap.data[idx + 2];
|
const normalBlue = this.bitmap.data[idx + 2];
|
||||||
|
|
||||||
// Subtract blurred pixel value from normal image
|
// Subtract blurred pixel value from normal image
|
||||||
this.bitmap.data[idx] = (normalRed > blurRed) ? normalRed - blurRed : 0;
|
this.bitmap.data[idx] =
|
||||||
this.bitmap.data[idx + 1] = (normalGreen > blurGreen) ? normalGreen - blurGreen : 0;
|
normalRed > blurRed ? normalRed - blurRed : 0;
|
||||||
this.bitmap.data[idx + 2] = (normalBlue > blurBlue) ? normalBlue - blurBlue : 0;
|
this.bitmap.data[idx + 1] =
|
||||||
});
|
normalGreen > blurGreen ? normalGreen - blurGreen : 0;
|
||||||
|
this.bitmap.data[idx + 2] =
|
||||||
|
normalBlue > blurBlue ? normalBlue - blurBlue : 0;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if (isWorkerEnvironment())
|
if (isWorkerEnvironment())
|
||||||
self.sendStatusMessage("Sharpening image... (Merging with unsharp mask)");
|
self.sendStatusMessage(
|
||||||
image.scan(0, 0, image.bitmap.width, image.bitmap.height, function(x, y, idx) {
|
"Sharpening image... (Merging with unsharp mask)",
|
||||||
let maskRed = blurMask.bitmap.data[idx];
|
);
|
||||||
let maskGreen = blurMask.bitmap.data[idx + 1];
|
image.scan(
|
||||||
let maskBlue = blurMask.bitmap.data[idx + 2];
|
0,
|
||||||
|
0,
|
||||||
|
image.bitmap.width,
|
||||||
|
image.bitmap.height,
|
||||||
|
function (x, y, idx) {
|
||||||
|
let maskRed = blurMask.bitmap.data[idx];
|
||||||
|
let maskGreen = blurMask.bitmap.data[idx + 1];
|
||||||
|
let maskBlue = blurMask.bitmap.data[idx + 2];
|
||||||
|
|
||||||
const normalRed = this.bitmap.data[idx];
|
const normalRed = this.bitmap.data[idx];
|
||||||
const normalGreen = this.bitmap.data[idx + 1];
|
const normalGreen = this.bitmap.data[idx + 1];
|
||||||
const normalBlue = this.bitmap.data[idx + 2];
|
const normalBlue = this.bitmap.data[idx + 2];
|
||||||
|
|
||||||
// Calculate luminance
|
// Calculate luminance
|
||||||
const maskLuminance = (0.2126 * maskRed + 0.7152 * maskGreen + 0.0722 * maskBlue);
|
const maskLuminance =
|
||||||
const normalLuminance = (0.2126 * normalRed + 0.7152 * normalGreen + 0.0722 * normalBlue);
|
0.2126 * maskRed +
|
||||||
|
0.7152 * maskGreen +
|
||||||
|
0.0722 * maskBlue;
|
||||||
|
const normalLuminance =
|
||||||
|
0.2126 * normalRed +
|
||||||
|
0.7152 * normalGreen +
|
||||||
|
0.0722 * normalBlue;
|
||||||
|
|
||||||
let luminanceDiff;
|
let luminanceDiff;
|
||||||
if (maskLuminance > normalLuminance) {
|
if (maskLuminance > normalLuminance) {
|
||||||
luminanceDiff = maskLuminance - normalLuminance;
|
luminanceDiff = maskLuminance - normalLuminance;
|
||||||
} else {
|
} else {
|
||||||
luminanceDiff = normalLuminance - maskLuminance;
|
luminanceDiff = normalLuminance - maskLuminance;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scale mask colours by amount
|
// Scale mask colours by amount
|
||||||
maskRed = maskRed * amount;
|
maskRed = maskRed * amount;
|
||||||
maskGreen = maskGreen * amount;
|
maskGreen = maskGreen * amount;
|
||||||
maskBlue = maskBlue * amount;
|
maskBlue = maskBlue * amount;
|
||||||
|
|
||||||
// Only change pixel value if the difference is higher than threshold
|
// Only change pixel value if the difference is higher than threshold
|
||||||
if ((luminanceDiff / 255) * 100 >= threshold) {
|
if ((luminanceDiff / 255) * 100 >= threshold) {
|
||||||
this.bitmap.data[idx] = (normalRed + maskRed) <= 255 ? normalRed + maskRed : 255;
|
this.bitmap.data[idx] =
|
||||||
this.bitmap.data[idx + 1] = (normalGreen + maskGreen) <= 255 ? normalGreen + maskGreen : 255;
|
normalRed + maskRed <= 255 ?
|
||||||
this.bitmap.data[idx + 2] = (normalBlue + maskBlue) <= 255 ? normalBlue + maskBlue : 255;
|
normalRed + maskRed :
|
||||||
}
|
255;
|
||||||
});
|
this.bitmap.data[idx + 1] =
|
||||||
|
normalGreen + maskGreen <= 255 ?
|
||||||
|
normalGreen + maskGreen :
|
||||||
|
255;
|
||||||
|
this.bitmap.data[idx + 2] =
|
||||||
|
normalBlue + maskBlue <= 255 ?
|
||||||
|
normalBlue + maskBlue :
|
||||||
|
255;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.mime === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
imageBuffer = await image.getBuffer(JimpMime.png);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
imageBuffer = await image.getBuffer(image.mime);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -163,7 +196,6 @@ class SharpenImage extends Operation {
|
|||||||
|
|
||||||
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SharpenImage;
|
export default SharpenImage;
|
||||||
|
|||||||
@ -7,14 +7,13 @@
|
|||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.mjs";
|
||||||
import OperationError from "../errors/OperationError.mjs";
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
import Utils from "../Utils.mjs";
|
import Utils from "../Utils.mjs";
|
||||||
import {isImage} from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import Jimp from "jimp/es/index.js";
|
import { Jimp, JimpMime } from "jimp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Split Colour Channels operation
|
* Split Colour Channels operation
|
||||||
*/
|
*/
|
||||||
class SplitColourChannels extends Operation {
|
class SplitColourChannels extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SplitColourChannels constructor
|
* SplitColourChannels constructor
|
||||||
*/
|
*/
|
||||||
@ -23,7 +22,8 @@ class SplitColourChannels extends Operation {
|
|||||||
|
|
||||||
this.name = "Split Colour Channels";
|
this.name = "Split Colour Channels";
|
||||||
this.module = "Image";
|
this.module = "Image";
|
||||||
this.description = "Splits the given image into its red, green and blue colour channels.";
|
this.description =
|
||||||
|
"Splits the given image into its red, green and blue colour channels.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Channel_(digital_image)";
|
this.infoURL = "https://wikipedia.org/wiki/Channel_(digital_image)";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "List<File>";
|
this.outputType = "List<File>";
|
||||||
@ -48,26 +48,44 @@ class SplitColourChannels extends Operation {
|
|||||||
const split = parsedImage
|
const split = parsedImage
|
||||||
.clone()
|
.clone()
|
||||||
.color([
|
.color([
|
||||||
{apply: "blue", params: [-255]},
|
{ apply: "blue", params: [-255] },
|
||||||
{apply: "green", params: [-255]}
|
{ apply: "green", params: [-255] },
|
||||||
])
|
])
|
||||||
.getBufferAsync(Jimp.MIME_PNG);
|
.getBuffer(JimpMime.png);
|
||||||
resolve(new File([new Uint8Array((await split).values())], "red.png", {type: "image/png"}));
|
resolve(
|
||||||
|
new File(
|
||||||
|
[new Uint8Array((await split).values())],
|
||||||
|
"red.png",
|
||||||
|
{ type: "image/png" },
|
||||||
|
),
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
reject(new OperationError(`Could not split red channel: ${err}`));
|
reject(
|
||||||
|
new OperationError(`Could not split red channel: ${err}`),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const green = new Promise(async (resolve, reject) => {
|
const green = new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const split = parsedImage.clone()
|
const split = parsedImage
|
||||||
|
.clone()
|
||||||
.color([
|
.color([
|
||||||
{apply: "red", params: [-255]},
|
{ apply: "red", params: [-255] },
|
||||||
{apply: "blue", params: [-255]},
|
{ apply: "blue", params: [-255] },
|
||||||
]).getBufferAsync(Jimp.MIME_PNG);
|
])
|
||||||
resolve(new File([new Uint8Array((await split).values())], "green.png", {type: "image/png"}));
|
.getBuffer(JimpMime.png);
|
||||||
|
resolve(
|
||||||
|
new File(
|
||||||
|
[new Uint8Array((await split).values())],
|
||||||
|
"green.png",
|
||||||
|
{ type: "image/png" },
|
||||||
|
),
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
reject(new OperationError(`Could not split green channel: ${err}`));
|
reject(
|
||||||
|
new OperationError(`Could not split green channel: ${err}`),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -75,12 +93,21 @@ class SplitColourChannels extends Operation {
|
|||||||
try {
|
try {
|
||||||
const split = parsedImage
|
const split = parsedImage
|
||||||
.color([
|
.color([
|
||||||
{apply: "red", params: [-255]},
|
{ apply: "red", params: [-255] },
|
||||||
{apply: "green", params: [-255]},
|
{ apply: "green", params: [-255] },
|
||||||
]).getBufferAsync(Jimp.MIME_PNG);
|
])
|
||||||
resolve(new File([new Uint8Array((await split).values())], "blue.png", {type: "image/png"}));
|
.getBuffer(JimpMime.png);
|
||||||
|
resolve(
|
||||||
|
new File(
|
||||||
|
[new Uint8Array((await split).values())],
|
||||||
|
"blue.png",
|
||||||
|
{ type: "image/png" },
|
||||||
|
),
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
reject(new OperationError(`Could not split blue channel: ${err}`));
|
reject(
|
||||||
|
new OperationError(`Could not split blue channel: ${err}`),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -96,7 +123,6 @@ class SplitColourChannels extends Operation {
|
|||||||
async present(files) {
|
async present(files) {
|
||||||
return await Utils.displayFilesAsHTML(files);
|
return await Utils.displayFilesAsHTML(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SplitColourChannels;
|
export default SplitColourChannels;
|
||||||
|
|||||||
123
src/core/operations/TextIntegerConverter.mjs
Normal file
123
src/core/operations/TextIntegerConverter.mjs
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
/**
|
||||||
|
* @author p-leriche [philip.leriche@cantab.net]
|
||||||
|
* @copyright Crown Copyright 2025
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
|
||||||
|
/* ---------- helper functions ---------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert text to BigInt (big-endian byte interpretation)
|
||||||
|
*/
|
||||||
|
function textToBigInt(text) {
|
||||||
|
if (text.length === 0) return 0n;
|
||||||
|
|
||||||
|
let result = 0n;
|
||||||
|
for (let i = 0; i < text.length; i++) {
|
||||||
|
const charCode = BigInt(text.charCodeAt(i));
|
||||||
|
if (charCode > 255n) {
|
||||||
|
throw new OperationError(
|
||||||
|
`Character at position ${i} exceeds Latin-1 range (0-255).\n` +
|
||||||
|
"Only ASCII and Latin-1 characters are supported.");
|
||||||
|
}
|
||||||
|
result = (result << 8n) | charCode;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert BigInt to text (big-endian byte interpretation)
|
||||||
|
*/
|
||||||
|
function bigIntToText(value) {
|
||||||
|
if (value === 0n) return "";
|
||||||
|
|
||||||
|
const bytes = [];
|
||||||
|
let num = value;
|
||||||
|
|
||||||
|
while (num > 0n) {
|
||||||
|
bytes.unshift(Number(num & 0xFFn));
|
||||||
|
num >>= 8n;
|
||||||
|
}
|
||||||
|
|
||||||
|
return String.fromCharCode(...bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- operation class ---------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text/Integer Converter operation
|
||||||
|
*/
|
||||||
|
class TextIntegerConverter extends Operation {
|
||||||
|
/**
|
||||||
|
* TextIntegerConverter constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.description =
|
||||||
|
"Converts between text strings and large integers (decimal or hexadecimal).<br><br>" +
|
||||||
|
"Text is interpreted as a big-endian sequence of character codes. For example:<br>" +
|
||||||
|
"ABC is 0x414243 (hex) is 4276803 (decimal)<br>" +
|
||||||
|
"<b>Input format detection:</b><br>" +
|
||||||
|
"Decimal: digits 0-9 only<br>" +
|
||||||
|
"Hexadecimal: 0x... prefix<br>" +
|
||||||
|
"Quoted or unquoted text: treated as string<br><br>" +
|
||||||
|
"<b>Character limitations:</b><br>" +
|
||||||
|
"Text input may only contain ASCII and Latin-1 characters (code point < 256).<br>" +
|
||||||
|
"Multi-byte Unicode characters will generate an error.<br><br>." ;
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/Endianness";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
name: "Output format",
|
||||||
|
type: "option",
|
||||||
|
value: ["String", "Decimal", "Hexadecimal"]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
this.name = "Text-Integer Conversion";
|
||||||
|
this.module = "Default";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const outputFormat = args[0];
|
||||||
|
const trimmed = input.trim();
|
||||||
|
|
||||||
|
let bigIntValue;
|
||||||
|
|
||||||
|
if (!trimmed) {
|
||||||
|
// Null input - treat as zero
|
||||||
|
bigIntValue = 0;
|
||||||
|
} else if (/^0x[0-9a-f]+$/i.test(trimmed) ||
|
||||||
|
/^[+-]?[0-9]+$/.test(trimmed)) {
|
||||||
|
// Hex or decimal integer
|
||||||
|
bigIntValue = BigInt(trimmed);
|
||||||
|
} else if (/^["'].*["']$/.test(trimmed)) {
|
||||||
|
// Quoted string: Remove quotes and convert text to BigInt
|
||||||
|
const text = trimmed.slice(1, -1);
|
||||||
|
bigIntValue = textToBigInt(text);
|
||||||
|
} else {
|
||||||
|
// Assume it's unquoted text
|
||||||
|
bigIntValue = textToBigInt(trimmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to output format
|
||||||
|
if (outputFormat === "String") {
|
||||||
|
return bigIntToText(bigIntValue);
|
||||||
|
} else if (outputFormat === "Decimal") {
|
||||||
|
return bigIntValue.toString();
|
||||||
|
} else { // Hexadecimal
|
||||||
|
return "0x" + bigIntValue.toString(16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TextIntegerConverter;
|
||||||
@ -23,7 +23,7 @@ class ToQuotedPrintable extends Operation {
|
|||||||
|
|
||||||
this.name = "To Quoted Printable";
|
this.name = "To Quoted Printable";
|
||||||
this.module = "Default";
|
this.module = "Default";
|
||||||
this.description = "Quoted-Printable, or QP encoding, is an encoding using printable ASCII characters (alphanumeric and the equals sign '=') to transmit 8-bit data over a 7-bit data path or, generally, over a medium which is not 8-bit clean. It is defined as a MIME content transfer encoding for use in e-mail.<br><br>QP works by using the equals sign '=' as an escape character. It also limits line length to 76, as some software has limits on line length.";
|
this.description = "Quoted-Printable, or QP encoding, is an encoding using printable ASCII characters (alphanumeric and the equals sign '=') to transmit 8-bit data over a 7-bit data path or, generally, over a medium which is not 8-bit clean. It is defined as a MIME content transfer encoding for use in email.<br><br>QP works by using the equals sign '=' as an escape character. It also limits line length to 76, as some software has limits on line length.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Quoted-printable";
|
this.infoURL = "https://wikipedia.org/wiki/Quoted-printable";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "string";
|
this.outputType = "string";
|
||||||
|
|||||||
@ -30,6 +30,23 @@ class UnescapeUnicodeCharacters extends Operation {
|
|||||||
"value": ["\\u", "%u", "U+"]
|
"value": ["\\u", "%u", "U+"]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
this.checks = [
|
||||||
|
{
|
||||||
|
pattern: "\\\\u(?:[\\da-f]{4,6})",
|
||||||
|
flags: "i",
|
||||||
|
args: ["\\u"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "%u(?:[\\da-f]{4,6})",
|
||||||
|
flags: "i",
|
||||||
|
args: ["%u"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "U\\+(?:[\\da-f]{4,6})",
|
||||||
|
flags: "i",
|
||||||
|
args: ["U+"]
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -9,13 +9,12 @@ import OperationError from "../errors/OperationError.mjs";
|
|||||||
import Utils from "../Utils.mjs";
|
import Utils from "../Utils.mjs";
|
||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import Jimp from "jimp/es/index.js";
|
import { Jimp } from "jimp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View Bit Plane operation
|
* View Bit Plane operation
|
||||||
*/
|
*/
|
||||||
class ViewBitPlane extends Operation {
|
class ViewBitPlane extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ViewBitPlane constructor
|
* ViewBitPlane constructor
|
||||||
*/
|
*/
|
||||||
@ -24,7 +23,8 @@ class ViewBitPlane extends Operation {
|
|||||||
|
|
||||||
this.name = "View Bit Plane";
|
this.name = "View Bit Plane";
|
||||||
this.module = "Image";
|
this.module = "Image";
|
||||||
this.description = "Extracts and displays a bit plane of any given image. These show only a single bit from each pixel, and can be used to hide messages in Steganography.";
|
this.description =
|
||||||
|
"Extracts and displays a bit plane of any given image. These show only a single bit from each pixel, and can be used to hide messages in Steganography.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Bit_plane";
|
this.infoURL = "https://wikipedia.org/wiki/Bit_plane";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "ArrayBuffer";
|
this.outputType = "ArrayBuffer";
|
||||||
@ -33,13 +33,13 @@ class ViewBitPlane extends Operation {
|
|||||||
{
|
{
|
||||||
name: "Colour",
|
name: "Colour",
|
||||||
type: "option",
|
type: "option",
|
||||||
value: COLOUR_OPTIONS
|
value: COLOUR_OPTIONS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Bit",
|
name: "Bit",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 0
|
value: 0,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,36 +49,38 @@ class ViewBitPlane extends Operation {
|
|||||||
* @returns {ArrayBuffer}
|
* @returns {ArrayBuffer}
|
||||||
*/
|
*/
|
||||||
async run(input, args) {
|
async run(input, args) {
|
||||||
if (!isImage(input)) throw new OperationError("Please enter a valid image file.");
|
if (!isImage(input))
|
||||||
|
throw new OperationError("Please enter a valid image file.");
|
||||||
|
|
||||||
const [colour, bit] = args,
|
const [colour, bit] = args,
|
||||||
parsedImage = await Jimp.read(input),
|
parsedImage = await Jimp.read(input),
|
||||||
width = parsedImage.bitmap.width,
|
width = parsedImage.bitmap.width,
|
||||||
height = parsedImage.bitmap.height,
|
height = parsedImage.bitmap.height,
|
||||||
colourIndex = COLOUR_OPTIONS.indexOf(colour),
|
colourIndex = COLOUR_OPTIONS.indexOf(colour),
|
||||||
bitIndex = 7-bit;
|
bitIndex = 7 - bit;
|
||||||
|
|
||||||
if (bit < 0 || bit > 7) {
|
if (bit < 0 || bit > 7) {
|
||||||
throw new OperationError("Error: Bit argument must be between 0 and 7");
|
throw new OperationError(
|
||||||
|
"Error: Bit argument must be between 0 and 7",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let pixel, bin, newPixelValue;
|
let pixel, bin, newPixelValue;
|
||||||
|
|
||||||
parsedImage.scan(0, 0, width, height, function(x, y, idx) {
|
parsedImage.scan(0, 0, width, height, function (x, y, idx) {
|
||||||
pixel = this.bitmap.data[idx + colourIndex];
|
pixel = this.bitmap.data[idx + colourIndex];
|
||||||
bin = Utils.bin(pixel);
|
bin = Utils.bin(pixel);
|
||||||
newPixelValue = 255;
|
newPixelValue = 255;
|
||||||
|
|
||||||
if (bin.charAt(bitIndex) === "1") newPixelValue = 0;
|
if (bin.charAt(bitIndex) === "1") newPixelValue = 0;
|
||||||
|
|
||||||
for (let i=0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
this.bitmap.data[idx + i] = newPixelValue;
|
this.bitmap.data[idx + i] = newPixelValue;
|
||||||
}
|
}
|
||||||
this.bitmap.data[idx + 3] = 255;
|
this.bitmap.data[idx + 3] = 255;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const imageBuffer = await parsedImage.getBufferAsync(Jimp.AUTO);
|
const imageBuffer = await parsedImage.getBuffer(parsedImage.mime);
|
||||||
|
|
||||||
return new Uint8Array(imageBuffer).buffer;
|
return new Uint8Array(imageBuffer).buffer;
|
||||||
}
|
}
|
||||||
@ -94,14 +96,8 @@ class ViewBitPlane extends Operation {
|
|||||||
|
|
||||||
return `<img src="data:${type};base64,${toBase64(data)}">`;
|
return `<img src="data:${type};base64,${toBase64(data)}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const COLOUR_OPTIONS = [
|
const COLOUR_OPTIONS = ["Red", "Green", "Blue", "Alpha"];
|
||||||
"Red",
|
|
||||||
"Green",
|
|
||||||
"Blue",
|
|
||||||
"Alpha"
|
|
||||||
];
|
|
||||||
|
|
||||||
export default ViewBitPlane;
|
export default ViewBitPlane;
|
||||||
|
|||||||
@ -49,15 +49,14 @@ class HTMLIngredient {
|
|||||||
toHtml() {
|
toHtml() {
|
||||||
let html = "",
|
let html = "",
|
||||||
i, m, eventFn;
|
i, m, eventFn;
|
||||||
|
const hintHtml = this.hint ? `data-toggle="tooltip" title="${this.hint}"` : "";
|
||||||
|
|
||||||
switch (this.type) {
|
switch (this.type) {
|
||||||
case "string":
|
case "string":
|
||||||
case "binaryString":
|
case "binaryString":
|
||||||
case "byteArray":
|
case "byteArray":
|
||||||
html += `<div class="form-group ing-wide">
|
html += `<div class="form-group ing-wide" ${hintHtml}>
|
||||||
<label for="${this.id}"
|
<label for="${this.id}" class="bmd-label-floating">${this.name}</label>
|
||||||
${this.hint ? `data-toggle="tooltip" title="${this.hint}"` : ""}
|
|
||||||
class="bmd-label-floating">${this.name}</label>
|
|
||||||
<input type="text"
|
<input type="text"
|
||||||
class="form-control arg"
|
class="form-control arg"
|
||||||
id="${this.id}"
|
id="${this.id}"
|
||||||
@ -70,10 +69,8 @@ class HTMLIngredient {
|
|||||||
break;
|
break;
|
||||||
case "shortString":
|
case "shortString":
|
||||||
case "binaryShortString":
|
case "binaryShortString":
|
||||||
html += `<div class="form-group ing-short">
|
html += `<div class="form-group ing-short" ${hintHtml}>
|
||||||
<label for="${this.id}"
|
<label for="${this.id}" class="bmd-label-floating inline">${this.name}</label>
|
||||||
${this.hint ? `data-toggle="tooltip" title="${this.hint}"` : ""}
|
|
||||||
class="bmd-label-floating inline">${this.name}</label>
|
|
||||||
<input type="text"
|
<input type="text"
|
||||||
class="form-control arg inline"
|
class="form-control arg inline"
|
||||||
id="${this.id}"
|
id="${this.id}"
|
||||||
@ -85,11 +82,9 @@ class HTMLIngredient {
|
|||||||
</div>`;
|
</div>`;
|
||||||
break;
|
break;
|
||||||
case "toggleString":
|
case "toggleString":
|
||||||
html += `<div class="form-group input-group ing-wide" data-help-title="Multi-type ingredients" data-help="Selecting a data type from the dropdown will change how the ingredient is interpreted by the operation.">
|
html += `<div class="form-group input-group ing-wide" data-help-title="Multi-type ingredients" data-help="Selecting a data type from the dropdown will change how the ingredient is interpreted by the operation." ${hintHtml}>
|
||||||
<div class="toggle-string">
|
<div class="toggle-string">
|
||||||
<label for="${this.id}"
|
<label for="${this.id}" class="bmd-label-floating toggle-string">${this.name}</label>
|
||||||
${this.hint ? `data-toggle="tooltip" title="${this.hint}"` : ""}
|
|
||||||
class="bmd-label-floating toggle-string">${this.name}</label>
|
|
||||||
<input type="text"
|
<input type="text"
|
||||||
class="form-control arg toggle-string"
|
class="form-control arg toggle-string"
|
||||||
id="${this.id}"
|
id="${this.id}"
|
||||||
@ -111,10 +106,8 @@ class HTMLIngredient {
|
|||||||
</div>`;
|
</div>`;
|
||||||
break;
|
break;
|
||||||
case "number":
|
case "number":
|
||||||
html += `<div class="form-group inline ing-medium">
|
html += `<div class="form-group inline ing-medium" ${hintHtml}>
|
||||||
<label for="${this.id}"
|
<label for="${this.id}" class="bmd-label-floating inline">${this.name}</label>
|
||||||
${this.hint ? `data-toggle="tooltip" title="${this.hint}"` : ""}
|
|
||||||
class="bmd-label-floating inline">${this.name}</label>
|
|
||||||
<input type="number"
|
<input type="number"
|
||||||
class="form-control arg inline"
|
class="form-control arg inline"
|
||||||
id="${this.id}"
|
id="${this.id}"
|
||||||
@ -128,9 +121,9 @@ class HTMLIngredient {
|
|||||||
</div>`;
|
</div>`;
|
||||||
break;
|
break;
|
||||||
case "boolean":
|
case "boolean":
|
||||||
html += `<div class="form-group inline boolean-arg ing-flexible">
|
html += `<div class="form-group inline boolean-arg ing-flexible" ${hintHtml}>
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label ${this.hint ? `data-toggle="tooltip" title="${this.hint}"` : ""}>
|
<label>
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
class="arg"
|
class="arg"
|
||||||
id="${this.id}"
|
id="${this.id}"
|
||||||
@ -144,10 +137,8 @@ class HTMLIngredient {
|
|||||||
</div>`;
|
</div>`;
|
||||||
break;
|
break;
|
||||||
case "option":
|
case "option":
|
||||||
html += `<div class="form-group ing-medium">
|
html += `<div class="form-group ing-medium" ${hintHtml}>
|
||||||
<label for="${this.id}"
|
<label for="${this.id}" class="bmd-label-floating inline">${this.name}</label>
|
||||||
${this.hint ? `data-toggle="tooltip" title="${this.hint}"` : ""}
|
|
||||||
class="bmd-label-floating inline">${this.name}</label>
|
|
||||||
<select
|
<select
|
||||||
class="form-control arg inline"
|
class="form-control arg inline"
|
||||||
id="${this.id}"
|
id="${this.id}"
|
||||||
@ -168,10 +159,8 @@ class HTMLIngredient {
|
|||||||
break;
|
break;
|
||||||
case "populateOption":
|
case "populateOption":
|
||||||
case "populateMultiOption":
|
case "populateMultiOption":
|
||||||
html += `<div class="form-group ing-medium" data-help-title="Population dropdowns" data-help="Selecting a value from this dropdown will populate some of the other ingredients for this operation with pre-canned values.">
|
html += `<div class="form-group ing-medium" data-help-title="Population dropdowns" data-help="Selecting a value from this dropdown will populate some of the other ingredients for this operation with pre-canned values." ${hintHtml}>
|
||||||
<label for="${this.id}"
|
<label for="${this.id}" class="bmd-label-floating">${this.name}</label>
|
||||||
${this.hint ? `data-toggle="tooltip" title="${this.hint}"` : ""}
|
|
||||||
class="bmd-label-floating">${this.name}</label>
|
|
||||||
<select
|
<select
|
||||||
class="form-control arg no-state-change populate-option"
|
class="form-control arg no-state-change populate-option"
|
||||||
id="${this.id}"
|
id="${this.id}"
|
||||||
@ -199,10 +188,8 @@ class HTMLIngredient {
|
|||||||
this.manager.addDynamicListener("#" + this.id, "change", eventFn, this);
|
this.manager.addDynamicListener("#" + this.id, "change", eventFn, this);
|
||||||
break;
|
break;
|
||||||
case "editableOption":
|
case "editableOption":
|
||||||
html += `<div class="form-group input-group ing-wide">
|
html += `<div class="form-group input-group ing-wide" ${hintHtml}>
|
||||||
<label for="${this.id}"
|
<label for="${this.id}" class="bmd-label-floating">${this.name}</label>
|
||||||
${this.hint ? `data-toggle="tooltip" title="${this.hint}"` : ""}
|
|
||||||
class="bmd-label-floating">${this.name}</label>
|
|
||||||
<input type="text"
|
<input type="text"
|
||||||
class="form-control arg"
|
class="form-control arg"
|
||||||
id="${this.id}"
|
id="${this.id}"
|
||||||
@ -230,10 +217,8 @@ class HTMLIngredient {
|
|||||||
this.manager.addDynamicListener(".editable-option-menu a", "click", this.editableOptionClick, this);
|
this.manager.addDynamicListener(".editable-option-menu a", "click", this.editableOptionClick, this);
|
||||||
break;
|
break;
|
||||||
case "editableOptionShort":
|
case "editableOptionShort":
|
||||||
html += `<div class="form-group input-group ing-short">
|
html += `<div class="form-group input-group ing-short" ${hintHtml}>
|
||||||
<label for="${this.id}"
|
<label for="${this.id}" class="bmd-label-floating inline">${this.name}</label>
|
||||||
${this.hint ? `data-toggle="tooltip" title="${this.hint}"` : ""}
|
|
||||||
class="bmd-label-floating inline">${this.name}</label>
|
|
||||||
<input type="text"
|
<input type="text"
|
||||||
class="form-control arg inline"
|
class="form-control arg inline"
|
||||||
id="${this.id}"
|
id="${this.id}"
|
||||||
@ -261,10 +246,8 @@ class HTMLIngredient {
|
|||||||
this.manager.addDynamicListener(".editable-option-menu a", "click", this.editableOptionClick, this);
|
this.manager.addDynamicListener(".editable-option-menu a", "click", this.editableOptionClick, this);
|
||||||
break;
|
break;
|
||||||
case "text":
|
case "text":
|
||||||
html += `<div class="form-group ing-very-wide">
|
html += `<div class="form-group ing-very-wide" ${hintHtml}>
|
||||||
<label for="${this.id}"
|
<label for="${this.id}" class="bmd-label-floating">${this.name}</label>
|
||||||
${this.hint ? `data-toggle="tooltip" title="${this.hint}"` : ""}
|
|
||||||
class="bmd-label-floating">${this.name}</label>
|
|
||||||
<textarea
|
<textarea
|
||||||
class="form-control arg"
|
class="form-control arg"
|
||||||
id="${this.id}"
|
id="${this.id}"
|
||||||
@ -275,10 +258,8 @@ class HTMLIngredient {
|
|||||||
</div>`;
|
</div>`;
|
||||||
break;
|
break;
|
||||||
case "argSelector":
|
case "argSelector":
|
||||||
html += `<div class="form-group inline ing-medium" data-help-title="Ingredient selector" data-help="Selecting options in this dropdown will configure which operation ingredients are visible.">
|
html += `<div class="form-group inline ing-medium" data-help-title="Ingredient selector" data-help="Selecting options in this dropdown will configure which operation ingredients are visible." ${hintHtml}>
|
||||||
<label for="${this.id}"
|
<label for="${this.id}" class="bmd-label-floating inline">${this.name}</label>
|
||||||
${this.hint ? `data-toggle="tooltip" title="${this.hint}"` : ""}
|
|
||||||
class="bmd-label-floating inline">${this.name}</label>
|
|
||||||
<select
|
<select
|
||||||
class="form-control arg inline arg-selector"
|
class="form-control arg inline arg-selector"
|
||||||
id="${this.id}"
|
id="${this.id}"
|
||||||
@ -298,7 +279,7 @@ class HTMLIngredient {
|
|||||||
this.manager.addDynamicListener(".arg-selector", "change", this.argSelectorChange, this);
|
this.manager.addDynamicListener(".arg-selector", "change", this.argSelectorChange, this);
|
||||||
break;
|
break;
|
||||||
case "label":
|
case "label":
|
||||||
html += `<div class="form-group ing-flexible">
|
html += `<div class="form-group ing-flexible" ${hintHtml}>
|
||||||
<label>${this.name}</label>
|
<label>${this.name}</label>
|
||||||
<input type="hidden"
|
<input type="hidden"
|
||||||
class="form-control arg"
|
class="form-control arg"
|
||||||
|
|||||||
@ -43,17 +43,23 @@ class HTMLOperation {
|
|||||||
/**
|
/**
|
||||||
* Renders the operation in HTML as a stub operation with no ingredients.
|
* Renders the operation in HTML as a stub operation with no ingredients.
|
||||||
*
|
*
|
||||||
|
* @param {boolean} removeIcon - show icon for removing operation
|
||||||
|
* @param {string} elementId - element ID for aria usage
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
toStubHtml(removeIcon) {
|
toStubHtml(removeIcon = false, elementId = null) {
|
||||||
let html = "<li class='operation'";
|
let html = "<li class='operation'";
|
||||||
|
|
||||||
|
if (elementId) {
|
||||||
|
html += ` id='${elementId}'`;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.description) {
|
if (this.description) {
|
||||||
const infoLink = this.infoURL ? `<hr>${titleFromWikiLink(this.infoURL)}` : "";
|
const infoLink = this.infoURL ? `<hr>${titleFromWikiLink(this.infoURL)}` : "";
|
||||||
|
|
||||||
html += ` data-container='body' data-toggle='popover' data-placement='right'
|
html += ` data-container='body' data-toggle='popover' data-placement='right'
|
||||||
data-content="${this.description}${infoLink}" data-html='true' data-trigger='hover'
|
data-content="${this.description}${infoLink}" data-html='true' data-trigger='hover'
|
||||||
data-boundary='viewport'`;
|
data-boundary='viewport' role='button'`;
|
||||||
}
|
}
|
||||||
|
|
||||||
html += ">" + this.name;
|
html += ">" + this.name;
|
||||||
|
|||||||
@ -130,6 +130,7 @@ class Manager {
|
|||||||
// Controls
|
// Controls
|
||||||
document.getElementById("bake").addEventListener("click", this.controls.bakeClick.bind(this.controls));
|
document.getElementById("bake").addEventListener("click", this.controls.bakeClick.bind(this.controls));
|
||||||
document.getElementById("auto-bake").addEventListener("change", this.controls.autoBakeChange.bind(this.controls));
|
document.getElementById("auto-bake").addEventListener("change", this.controls.autoBakeChange.bind(this.controls));
|
||||||
|
document.getElementById("auto-bake").addEventListener("keydown", this.controls.autoBakeKeyboardHandler.bind(this.controls));
|
||||||
document.getElementById("step").addEventListener("click", this.controls.stepClick.bind(this.controls));
|
document.getElementById("step").addEventListener("click", this.controls.stepClick.bind(this.controls));
|
||||||
document.getElementById("clr-recipe").addEventListener("click", this.controls.clearRecipeClick.bind(this.controls));
|
document.getElementById("clr-recipe").addEventListener("click", this.controls.clearRecipeClick.bind(this.controls));
|
||||||
document.getElementById("save").addEventListener("click", this.controls.saveClick.bind(this.controls));
|
document.getElementById("save").addEventListener("click", this.controls.saveClick.bind(this.controls));
|
||||||
|
|||||||
@ -145,7 +145,7 @@
|
|||||||
<button type="button" aria-label="Edit Favourites" class="btn btn-warning bmd-btn-icon" id="edit-favourites" data-toggle="tooltip" title="Edit favourites">
|
<button type="button" aria-label="Edit Favourites" class="btn btn-warning bmd-btn-icon" id="edit-favourites" data-toggle="tooltip" title="Edit favourites">
|
||||||
<i class="material-icons" aria-hidden="true">star</i>
|
<i class="material-icons" aria-hidden="true">star</i>
|
||||||
</button>
|
</button>
|
||||||
<div id="content-wrapper">
|
<div tabindex="0" id="content-wrapper">
|
||||||
<div id="banner" class="row">
|
<div id="banner" class="row">
|
||||||
<div class="col" style="text-align: left; padding-left: 10px;">
|
<div class="col" style="text-align: left; padding-left: 10px;">
|
||||||
<a href="#" data-toggle="modal" data-target="#download-modal" data-help-title="Downloading CyberChef" data-help="<p>CyberChef can be downloaded to run locally or hosted within your own network. It has no server-side component so all that is required is that the ZIP file is uncompressed and the files are accessible.</p><p>As a user, it is worth noting that unofficial versions of CyberChef could have been modified to introduce Input and/or Recipe exfiltration. We recommend always using the official, open source, up-to-date version of CyberChef hosted at <a href='https://gchq.github.io/CyberChef'>https://gchq.github.io/CyberChef</a> if accessible.</p><p>The Network tab in your browser's Developer console (F12) can be used to inspect the network requests made by a website. This can confirm that no data is uploaded when a CyberChef recipe is baked.</p>">Download CyberChef <i class="material-icons">file_download</i></a>
|
<a href="#" data-toggle="modal" data-target="#download-modal" data-help-title="Downloading CyberChef" data-help="<p>CyberChef can be downloaded to run locally or hosted within your own network. It has no server-side component so all that is required is that the ZIP file is uncompressed and the files are accessible.</p><p>As a user, it is worth noting that unofficial versions of CyberChef could have been modified to introduce Input and/or Recipe exfiltration. We recommend always using the official, open source, up-to-date version of CyberChef hosted at <a href='https://gchq.github.io/CyberChef'>https://gchq.github.io/CyberChef</a> if accessible.</p><p>The Network tab in your browser's Developer console (F12) can be used to inspect the network requests made by a website. This can confirm that no data is uploaded when a CyberChef recipe is baked.</p>">Download CyberChef <i class="material-icons">file_download</i></a>
|
||||||
@ -173,7 +173,7 @@
|
|||||||
Operations
|
Operations
|
||||||
<span class="op-count"></span>
|
<span class="op-count"></span>
|
||||||
</div>
|
</div>
|
||||||
<input id="search" type="search" class="form-control" placeholder="Search..." autocomplete="off" tabindex="2" data-help-title="Searching for operations" data-help="<p>Use the search box to find useful operations.</p><p>Both operation names and descriptions are queried using a fuzzy matching algorithm.</p>">
|
<input id="search" type="search" class="form-control" placeholder="Search..." autocomplete="off" tabindex="0" data-help-title="Searching for operations" data-help="<p>Use the search box to find useful operations.</p><p>Both operation names and descriptions are queried using a fuzzy matching algorithm.</p>" aria-owns="search-results">
|
||||||
<ul id="search-results" class="op-list"></ul>
|
<ul id="search-results" class="op-list"></ul>
|
||||||
<div id="categories" class="panel-group no-select"></div>
|
<div id="categories" class="panel-group no-select"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -584,22 +584,22 @@
|
|||||||
|
|
||||||
<ul class="nav nav-tabs" role="tablist">
|
<ul class="nav nav-tabs" role="tablist">
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link active" href="#faqs" aria-controls="profile" role="tab" data-toggle="tab">
|
<a id="tab-1" class="nav-link active" href="#faqs" aria-controls="profile" role="tab" data-toggle="tab">
|
||||||
FAQs
|
FAQs
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" href="#report-bug" aria-controls="messages" role="tab" data-toggle="tab">
|
<a id="tab-2" class="nav-link" href="#report-bug" aria-controls="messages" role="tab" data-toggle="tab">
|
||||||
Report a bug
|
Report a bug
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" href="#about" aria-controls="messages" role="tab" data-toggle="tab">
|
<a id="tab-3" class="nav-link" href="#about" aria-controls="messages" role="tab" data-toggle="tab">
|
||||||
About
|
About
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" href="#keybindings" aria-controls="messages" role="tab" data-toggle="tab">
|
<a id="tab-4" class="nav-link" href="#keybindings" aria-controls="messages" role="tab" data-toggle="tab">
|
||||||
Keybindings
|
Keybindings
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -1,485 +1,491 @@
|
|||||||
info face="Roboto" size=72 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=1,1,1,1 spacing=-2,-2
|
<?xml version="1.0"?>
|
||||||
common lineHeight=85 base=67 scaleW=512 scaleH=512 pages=1 packed=0
|
<font>
|
||||||
page id=0 file="Roboto72White.png"
|
<info face="Roboto" size="72" bold="0" italic="0" charset="" unicode="0" stretchH="100" smooth="1" aa="1" padding="1,1,1,1" spacing="-2,-2" outline="0" />
|
||||||
chars count=98
|
<common lineHeight="85" base="67" scaleW="512" scaleH="512" pages="1" packed="0" alphaChnl="0" redChnl="0" greenChnl="0" blueChnl="0" />
|
||||||
char id=0 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=66 xadvance=0 page=0 chnl=0
|
<pages>
|
||||||
char id=10 x=0 y=0 width=70 height=99 xoffset=2 yoffset=-11 xadvance=74 page=0 chnl=0
|
<page id="0" file="Roboto72White.png" /> </pages>
|
||||||
char id=32 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=66 xadvance=18 page=0 chnl=0
|
<chars count="98">
|
||||||
char id=33 x=493 y=99 width=10 height=55 xoffset=5 yoffset=14 xadvance=19 page=0 chnl=0
|
<char id="0" x="0" y="0" width="0" height="0" xoffset="-1" yoffset="66" xadvance="0" page="0" chnl="0" />
|
||||||
char id=34 x=446 y=319 width=16 height=19 xoffset=4 yoffset=12 xadvance=23 page=0 chnl=0
|
<char id="10" x="0" y="0" width="70" height="99" xoffset="2" yoffset="-11" xadvance="74" page="0" chnl="0" />
|
||||||
char id=35 x=204 y=265 width=41 height=54 xoffset=3 yoffset=14 xadvance=44 page=0 chnl=0
|
<char id="32" x="0" y="0" width="0" height="0" xoffset="-1" yoffset="66" xadvance="18" page="0" chnl="0" />
|
||||||
char id=36 x=269 y=0 width=35 height=69 xoffset=3 yoffset=6 xadvance=40 page=0 chnl=0
|
<char id="33" x="493" y="99" width="10" height="55" xoffset="5" yoffset="14" xadvance="19" page="0" chnl="0" />
|
||||||
char id=37 x=31 y=155 width=48 height=56 xoffset=3 yoffset=13 xadvance=53 page=0 chnl=0
|
<char id="34" x="446" y="319" width="16" height="19" xoffset="4" yoffset="12" xadvance="23" page="0" chnl="0" />
|
||||||
char id=38 x=79 y=155 width=43 height=56 xoffset=3 yoffset=13 xadvance=45 page=0 chnl=0
|
<char id="35" x="204" y="265" width="41" height="54" xoffset="3" yoffset="14" xadvance="44" page="0" chnl="0" />
|
||||||
char id=39 x=503 y=99 width=7 height=19 xoffset=3 yoffset=12 xadvance=13 page=0 chnl=0
|
<char id="36" x="269" y="0" width="35" height="69" xoffset="3" yoffset="6" xadvance="40" page="0" chnl="0" />
|
||||||
char id=40 x=70 y=0 width=21 height=78 xoffset=4 yoffset=7 xadvance=25 page=0 chnl=0
|
<char id="37" x="31" y="155" width="48" height="56" xoffset="3" yoffset="13" xadvance="53" page="0" chnl="0" />
|
||||||
char id=41 x=91 y=0 width=22 height=78 xoffset=-1 yoffset=7 xadvance=25 page=0 chnl=0
|
<char id="38" x="79" y="155" width="43" height="56" xoffset="3" yoffset="13" xadvance="45" page="0" chnl="0" />
|
||||||
char id=42 x=342 y=319 width=32 height=32 xoffset=-1 yoffset=14 xadvance=31 page=0 chnl=0
|
<char id="39" x="503" y="99" width="7" height="19" xoffset="3" yoffset="12" xadvance="13" page="0" chnl="0" />
|
||||||
char id=43 x=242 y=319 width=37 height=40 xoffset=2 yoffset=23 xadvance=41 page=0 chnl=0
|
<char id="40" x="70" y="0" width="21" height="78" xoffset="4" yoffset="7" xadvance="25" page="0" chnl="0" />
|
||||||
char id=44 x=433 y=319 width=13 height=21 xoffset=-1 yoffset=58 xadvance=14 page=0 chnl=0
|
<char id="41" x="91" y="0" width="22" height="78" xoffset="-1" yoffset="7" xadvance="25" page="0" chnl="0" />
|
||||||
char id=45 x=27 y=360 width=19 height=8 xoffset=0 yoffset=41 xadvance=19 page=0 chnl=0
|
<char id="42" x="342" y="319" width="32" height="32" xoffset="-1" yoffset="14" xadvance="31" page="0" chnl="0" />
|
||||||
char id=46 x=17 y=360 width=10 height=11 xoffset=4 yoffset=58 xadvance=19 page=0 chnl=0
|
<char id="43" x="242" y="319" width="37" height="40" xoffset="2" yoffset="23" xadvance="41" page="0" chnl="0" />
|
||||||
char id=47 x=355 y=0 width=30 height=58 xoffset=-1 yoffset=14 xadvance=30 page=0 chnl=0
|
<char id="44" x="433" y="319" width="13" height="21" xoffset="-1" yoffset="58" xadvance="14" page="0" chnl="0" />
|
||||||
char id=48 x=449 y=99 width=34 height=56 xoffset=3 yoffset=13 xadvance=40 page=0 chnl=0
|
<char id="45" x="27" y="360" width="19" height="8" xoffset="0" yoffset="41" xadvance="19" page="0" chnl="0" />
|
||||||
char id=49 x=474 y=211 width=22 height=54 xoffset=5 yoffset=14 xadvance=40 page=0 chnl=0
|
<char id="46" x="17" y="360" width="10" height="11" xoffset="4" yoffset="58" xadvance="19" page="0" chnl="0" />
|
||||||
char id=50 x=195 y=155 width=37 height=55 xoffset=2 yoffset=13 xadvance=41 page=0 chnl=0
|
<char id="47" x="355" y="0" width="30" height="58" xoffset="-1" yoffset="14" xadvance="30" page="0" chnl="0" />
|
||||||
char id=51 x=379 y=99 width=35 height=56 xoffset=2 yoffset=13 xadvance=40 page=0 chnl=0
|
<char id="48" x="449" y="99" width="34" height="56" xoffset="3" yoffset="13" xadvance="40" page="0" chnl="0" />
|
||||||
char id=52 x=128 y=265 width=39 height=54 xoffset=1 yoffset=14 xadvance=41 page=0 chnl=0
|
<char id="49" x="474" y="211" width="22" height="54" xoffset="5" yoffset="14" xadvance="40" page="0" chnl="0" />
|
||||||
char id=53 x=232 y=155 width=35 height=55 xoffset=4 yoffset=14 xadvance=40 page=0 chnl=0
|
<char id="50" x="195" y="155" width="37" height="55" xoffset="2" yoffset="13" xadvance="41" page="0" chnl="0" />
|
||||||
char id=54 x=267 y=155 width=35 height=55 xoffset=4 yoffset=14 xadvance=41 page=0 chnl=0
|
<char id="51" x="379" y="99" width="35" height="56" xoffset="2" yoffset="13" xadvance="40" page="0" chnl="0" />
|
||||||
char id=55 x=167 y=265 width=37 height=54 xoffset=2 yoffset=14 xadvance=41 page=0 chnl=0
|
<char id="52" x="128" y="265" width="39" height="54" xoffset="1" yoffset="14" xadvance="41" page="0" chnl="0" />
|
||||||
char id=56 x=414 y=99 width=35 height=56 xoffset=3 yoffset=13 xadvance=40 page=0 chnl=0
|
<char id="53" x="232" y="155" width="35" height="55" xoffset="4" yoffset="14" xadvance="40" page="0" chnl="0" />
|
||||||
char id=57 x=302 y=155 width=34 height=55 xoffset=3 yoffset=13 xadvance=41 page=0 chnl=0
|
<char id="54" x="267" y="155" width="35" height="55" xoffset="4" yoffset="14" xadvance="41" page="0" chnl="0" />
|
||||||
char id=58 x=495 y=265 width=10 height=41 xoffset=4 yoffset=28 xadvance=18 page=0 chnl=0
|
<char id="55" x="167" y="265" width="37" height="54" xoffset="2" yoffset="14" xadvance="41" page="0" chnl="0" />
|
||||||
char id=59 x=496 y=211 width=13 height=52 xoffset=0 yoffset=28 xadvance=15 page=0 chnl=0
|
<char id="56" x="414" y="99" width="35" height="56" xoffset="3" yoffset="13" xadvance="40" page="0" chnl="0" />
|
||||||
char id=60 x=279 y=319 width=31 height=35 xoffset=2 yoffset=27 xadvance=37 page=0 chnl=0
|
<char id="57" x="302" y="155" width="34" height="55" xoffset="3" yoffset="13" xadvance="41" page="0" chnl="0" />
|
||||||
char id=61 x=402 y=319 width=31 height=23 xoffset=4 yoffset=31 xadvance=39 page=0 chnl=0
|
<char id="58" x="495" y="265" width="10" height="41" xoffset="4" yoffset="28" xadvance="18" page="0" chnl="0" />
|
||||||
char id=62 x=310 y=319 width=32 height=35 xoffset=4 yoffset=27 xadvance=38 page=0 chnl=0
|
<char id="59" x="496" y="211" width="13" height="52" xoffset="0" yoffset="28" xadvance="15" page="0" chnl="0" />
|
||||||
char id=63 x=0 y=155 width=31 height=56 xoffset=2 yoffset=13 xadvance=34 page=0 chnl=0
|
<char id="60" x="279" y="319" width="31" height="35" xoffset="2" yoffset="27" xadvance="37" page="0" chnl="0" />
|
||||||
char id=64 x=210 y=0 width=59 height=69 xoffset=3 yoffset=15 xadvance=65 page=0 chnl=0
|
<char id="61" x="402" y="319" width="31" height="23" xoffset="4" yoffset="31" xadvance="39" page="0" chnl="0" />
|
||||||
char id=65 x=336 y=155 width=49 height=54 xoffset=-1 yoffset=14 xadvance=47 page=0 chnl=0
|
<char id="62" x="310" y="319" width="32" height="35" xoffset="4" yoffset="27" xadvance="38" page="0" chnl="0" />
|
||||||
char id=66 x=385 y=155 width=37 height=54 xoffset=5 yoffset=14 xadvance=45 page=0 chnl=0
|
<char id="63" x="0" y="155" width="31" height="56" xoffset="2" yoffset="13" xadvance="34" page="0" chnl="0" />
|
||||||
char id=67 x=0 y=99 width=42 height=56 xoffset=3 yoffset=13 xadvance=46 page=0 chnl=0
|
<char id="64" x="210" y="0" width="59" height="69" xoffset="3" yoffset="15" xadvance="65" page="0" chnl="0" />
|
||||||
char id=68 x=422 y=155 width=39 height=54 xoffset=5 yoffset=14 xadvance=47 page=0 chnl=0
|
<char id="65" x="336" y="155" width="49" height="54" xoffset="-1" yoffset="14" xadvance="47" page="0" chnl="0" />
|
||||||
char id=69 x=461 y=155 width=35 height=54 xoffset=5 yoffset=14 xadvance=41 page=0 chnl=0
|
<char id="66" x="385" y="155" width="37" height="54" xoffset="5" yoffset="14" xadvance="45" page="0" chnl="0" />
|
||||||
char id=70 x=0 y=211 width=34 height=54 xoffset=5 yoffset=14 xadvance=40 page=0 chnl=0
|
<char id="67" x="0" y="99" width="42" height="56" xoffset="3" yoffset="13" xadvance="46" page="0" chnl="0" />
|
||||||
char id=71 x=42 y=99 width=42 height=56 xoffset=3 yoffset=13 xadvance=49 page=0 chnl=0
|
<char id="68" x="422" y="155" width="39" height="54" xoffset="5" yoffset="14" xadvance="47" page="0" chnl="0" />
|
||||||
char id=72 x=34 y=211 width=41 height=54 xoffset=5 yoffset=14 xadvance=51 page=0 chnl=0
|
<char id="69" x="461" y="155" width="35" height="54" xoffset="5" yoffset="14" xadvance="41" page="0" chnl="0" />
|
||||||
char id=73 x=496 y=155 width=9 height=54 xoffset=5 yoffset=14 xadvance=19 page=0 chnl=0
|
<char id="70" x="0" y="211" width="34" height="54" xoffset="5" yoffset="14" xadvance="40" page="0" chnl="0" />
|
||||||
char id=74 x=122 y=155 width=34 height=55 xoffset=1 yoffset=14 xadvance=40 page=0 chnl=0
|
<char id="71" x="42" y="99" width="42" height="56" xoffset="3" yoffset="13" xadvance="49" page="0" chnl="0" />
|
||||||
char id=75 x=75 y=211 width=41 height=54 xoffset=5 yoffset=14 xadvance=45 page=0 chnl=0
|
<char id="72" x="34" y="211" width="41" height="54" xoffset="5" yoffset="14" xadvance="51" page="0" chnl="0" />
|
||||||
char id=76 x=116 y=211 width=33 height=54 xoffset=5 yoffset=14 xadvance=39 page=0 chnl=0
|
<char id="73" x="496" y="155" width="9" height="54" xoffset="5" yoffset="14" xadvance="19" page="0" chnl="0" />
|
||||||
char id=77 x=149 y=211 width=53 height=54 xoffset=5 yoffset=14 xadvance=63 page=0 chnl=0
|
<char id="74" x="122" y="155" width="34" height="55" xoffset="1" yoffset="14" xadvance="40" page="0" chnl="0" />
|
||||||
char id=78 x=202 y=211 width=41 height=54 xoffset=5 yoffset=14 xadvance=51 page=0 chnl=0
|
<char id="75" x="75" y="211" width="41" height="54" xoffset="5" yoffset="14" xadvance="45" page="0" chnl="0" />
|
||||||
char id=79 x=84 y=99 width=43 height=56 xoffset=3 yoffset=13 xadvance=49 page=0 chnl=0
|
<char id="76" x="116" y="211" width="33" height="54" xoffset="5" yoffset="14" xadvance="39" page="0" chnl="0" />
|
||||||
char id=80 x=243 y=211 width=39 height=54 xoffset=5 yoffset=14 xadvance=45 page=0 chnl=0
|
<char id="77" x="149" y="211" width="53" height="54" xoffset="5" yoffset="14" xadvance="63" page="0" chnl="0" />
|
||||||
char id=81 x=304 y=0 width=44 height=64 xoffset=3 yoffset=13 xadvance=49 page=0 chnl=0
|
<char id="78" x="202" y="211" width="41" height="54" xoffset="5" yoffset="14" xadvance="51" page="0" chnl="0" />
|
||||||
char id=82 x=282 y=211 width=40 height=54 xoffset=5 yoffset=14 xadvance=45 page=0 chnl=0
|
<char id="79" x="84" y="99" width="43" height="56" xoffset="3" yoffset="13" xadvance="49" page="0" chnl="0" />
|
||||||
char id=83 x=127 y=99 width=39 height=56 xoffset=2 yoffset=13 xadvance=43 page=0 chnl=0
|
<char id="80" x="243" y="211" width="39" height="54" xoffset="5" yoffset="14" xadvance="45" page="0" chnl="0" />
|
||||||
char id=84 x=322 y=211 width=42 height=54 xoffset=1 yoffset=14 xadvance=44 page=0 chnl=0
|
<char id="81" x="304" y="0" width="44" height="64" xoffset="3" yoffset="13" xadvance="49" page="0" chnl="0" />
|
||||||
char id=85 x=156 y=155 width=39 height=55 xoffset=4 yoffset=14 xadvance=47 page=0 chnl=0
|
<char id="82" x="282" y="211" width="40" height="54" xoffset="5" yoffset="14" xadvance="45" page="0" chnl="0" />
|
||||||
char id=86 x=364 y=211 width=47 height=54 xoffset=-1 yoffset=14 xadvance=46 page=0 chnl=0
|
<char id="83" x="127" y="99" width="39" height="56" xoffset="2" yoffset="13" xadvance="43" page="0" chnl="0" />
|
||||||
char id=87 x=411 y=211 width=63 height=54 xoffset=1 yoffset=14 xadvance=64 page=0 chnl=0
|
<char id="84" x="322" y="211" width="42" height="54" xoffset="1" yoffset="14" xadvance="44" page="0" chnl="0" />
|
||||||
char id=88 x=0 y=265 width=44 height=54 xoffset=1 yoffset=14 xadvance=45 page=0 chnl=0
|
<char id="85" x="156" y="155" width="39" height="55" xoffset="4" yoffset="14" xadvance="47" page="0" chnl="0" />
|
||||||
char id=89 x=44 y=265 width=45 height=54 xoffset=-1 yoffset=14 xadvance=43 page=0 chnl=0
|
<char id="86" x="364" y="211" width="47" height="54" xoffset="-1" yoffset="14" xadvance="46" page="0" chnl="0" />
|
||||||
char id=90 x=89 y=265 width=39 height=54 xoffset=2 yoffset=14 xadvance=43 page=0 chnl=0
|
<char id="87" x="411" y="211" width="63" height="54" xoffset="1" yoffset="14" xadvance="64" page="0" chnl="0" />
|
||||||
char id=91 x=161 y=0 width=16 height=72 xoffset=4 yoffset=7 xadvance=19 page=0 chnl=0
|
<char id="88" x="0" y="265" width="44" height="54" xoffset="1" yoffset="14" xadvance="45" page="0" chnl="0" />
|
||||||
char id=92 x=385 y=0 width=30 height=58 xoffset=0 yoffset=14 xadvance=30 page=0 chnl=0
|
<char id="89" x="44" y="265" width="45" height="54" xoffset="-1" yoffset="14" xadvance="43" page="0" chnl="0" />
|
||||||
char id=93 x=177 y=0 width=16 height=72 xoffset=0 yoffset=7 xadvance=20 page=0 chnl=0
|
<char id="90" x="89" y="265" width="39" height="54" xoffset="2" yoffset="14" xadvance="43" page="0" chnl="0" />
|
||||||
char id=94 x=374 y=319 width=28 height=28 xoffset=1 yoffset=14 xadvance=30 page=0 chnl=0
|
<char id="91" x="161" y="0" width="16" height="72" xoffset="4" yoffset="7" xadvance="19" page="0" chnl="0" />
|
||||||
char id=95 x=46 y=360 width=34 height=8 xoffset=0 yoffset=65 xadvance=34 page=0 chnl=0
|
<char id="92" x="385" y="0" width="30" height="58" xoffset="0" yoffset="14" xadvance="30" page="0" chnl="0" />
|
||||||
char id=96 x=0 y=360 width=17 height=13 xoffset=1 yoffset=11 xadvance=22 page=0 chnl=0
|
<char id="93" x="177" y="0" width="16" height="72" xoffset="0" yoffset="7" xadvance="20" page="0" chnl="0" />
|
||||||
char id=97 x=268 y=265 width=34 height=42 xoffset=3 yoffset=27 xadvance=39 page=0 chnl=0
|
<char id="94" x="374" y="319" width="28" height="28" xoffset="1" yoffset="14" xadvance="30" page="0" chnl="0" />
|
||||||
char id=98 x=415 y=0 width=34 height=57 xoffset=4 yoffset=12 xadvance=40 page=0 chnl=0
|
<char id="95" x="46" y="360" width="34" height="8" xoffset="0" yoffset="65" xadvance="34" page="0" chnl="0" />
|
||||||
char id=99 x=302 y=265 width=34 height=42 xoffset=2 yoffset=27 xadvance=38 page=0 chnl=0
|
<char id="96" x="0" y="360" width="17" height="13" xoffset="1" yoffset="11" xadvance="22" page="0" chnl="0" />
|
||||||
char id=100 x=449 y=0 width=34 height=57 xoffset=2 yoffset=12 xadvance=40 page=0 chnl=0
|
<char id="97" x="268" y="265" width="34" height="42" xoffset="3" yoffset="27" xadvance="39" page="0" chnl="0" />
|
||||||
char id=101 x=336 y=265 width=34 height=42 xoffset=2 yoffset=27 xadvance=38 page=0 chnl=0
|
<char id="98" x="415" y="0" width="34" height="57" xoffset="4" yoffset="12" xadvance="40" page="0" chnl="0" />
|
||||||
char id=102 x=483 y=0 width=25 height=57 xoffset=1 yoffset=11 xadvance=26 page=0 chnl=0
|
<char id="99" x="302" y="265" width="34" height="42" xoffset="2" yoffset="27" xadvance="38" page="0" chnl="0" />
|
||||||
char id=103 x=166 y=99 width=34 height=56 xoffset=2 yoffset=27 xadvance=40 page=0 chnl=0
|
<char id="100" x="449" y="0" width="34" height="57" xoffset="2" yoffset="12" xadvance="40" page="0" chnl="0" />
|
||||||
char id=104 x=200 y=99 width=32 height=56 xoffset=4 yoffset=12 xadvance=40 page=0 chnl=0
|
<char id="101" x="336" y="265" width="34" height="42" xoffset="2" yoffset="27" xadvance="38" page="0" chnl="0" />
|
||||||
char id=105 x=483 y=99 width=10 height=55 xoffset=4 yoffset=13 xadvance=18 page=0 chnl=0
|
<char id="102" x="483" y="0" width="25" height="57" xoffset="1" yoffset="11" xadvance="26" page="0" chnl="0" />
|
||||||
char id=106 x=193 y=0 width=17 height=71 xoffset=-4 yoffset=13 xadvance=17 page=0 chnl=0
|
<char id="103" x="166" y="99" width="34" height="56" xoffset="2" yoffset="27" xadvance="40" page="0" chnl="0" />
|
||||||
char id=107 x=232 y=99 width=34 height=56 xoffset=4 yoffset=12 xadvance=37 page=0 chnl=0
|
<char id="104" x="200" y="99" width="32" height="56" xoffset="4" yoffset="12" xadvance="40" page="0" chnl="0" />
|
||||||
char id=108 x=266 y=99 width=9 height=56 xoffset=4 yoffset=12 xadvance=17 page=0 chnl=0
|
<char id="105" x="483" y="99" width="10" height="55" xoffset="4" yoffset="13" xadvance="18" page="0" chnl="0" />
|
||||||
char id=109 x=439 y=265 width=56 height=41 xoffset=4 yoffset=27 xadvance=64 page=0 chnl=0
|
<char id="106" x="193" y="0" width="17" height="71" xoffset="-4" yoffset="13" xadvance="17" page="0" chnl="0" />
|
||||||
char id=110 x=0 y=319 width=32 height=41 xoffset=4 yoffset=27 xadvance=40 page=0 chnl=0
|
<char id="107" x="232" y="99" width="34" height="56" xoffset="4" yoffset="12" xadvance="37" page="0" chnl="0" />
|
||||||
char id=111 x=370 y=265 width=37 height=42 xoffset=2 yoffset=27 xadvance=41 page=0 chnl=0
|
<char id="108" x="266" y="99" width="9" height="56" xoffset="4" yoffset="12" xadvance="17" page="0" chnl="0" />
|
||||||
char id=112 x=275 y=99 width=34 height=56 xoffset=4 yoffset=27 xadvance=40 page=0 chnl=0
|
<char id="109" x="439" y="265" width="56" height="41" xoffset="4" yoffset="27" xadvance="64" page="0" chnl="0" />
|
||||||
char id=113 x=309 y=99 width=34 height=56 xoffset=2 yoffset=27 xadvance=41 page=0 chnl=0
|
<char id="110" x="0" y="319" width="32" height="41" xoffset="4" yoffset="27" xadvance="40" page="0" chnl="0" />
|
||||||
char id=114 x=32 y=319 width=21 height=41 xoffset=4 yoffset=27 xadvance=25 page=0 chnl=0
|
<char id="111" x="370" y="265" width="37" height="42" xoffset="2" yoffset="27" xadvance="41" page="0" chnl="0" />
|
||||||
char id=115 x=407 y=265 width=32 height=42 xoffset=2 yoffset=27 xadvance=37 page=0 chnl=0
|
<char id="112" x="275" y="99" width="34" height="56" xoffset="4" yoffset="27" xadvance="40" page="0" chnl="0" />
|
||||||
char id=116 x=245 y=265 width=23 height=51 xoffset=0 yoffset=18 xadvance=25 page=0 chnl=0
|
<char id="113" x="309" y="99" width="34" height="56" xoffset="2" yoffset="27" xadvance="41" page="0" chnl="0" />
|
||||||
char id=117 x=53 y=319 width=32 height=41 xoffset=4 yoffset=28 xadvance=40 page=0 chnl=0
|
<char id="114" x="32" y="319" width="21" height="41" xoffset="4" yoffset="27" xadvance="25" page="0" chnl="0" />
|
||||||
char id=118 x=85 y=319 width=35 height=40 xoffset=0 yoffset=28 xadvance=35 page=0 chnl=0
|
<char id="115" x="407" y="265" width="32" height="42" xoffset="2" yoffset="27" xadvance="37" page="0" chnl="0" />
|
||||||
char id=119 x=120 y=319 width=54 height=40 xoffset=0 yoffset=28 xadvance=54 page=0 chnl=0
|
<char id="116" x="245" y="265" width="23" height="51" xoffset="0" yoffset="18" xadvance="25" page="0" chnl="0" />
|
||||||
char id=120 x=174 y=319 width=36 height=40 xoffset=0 yoffset=28 xadvance=36 page=0 chnl=0
|
<char id="117" x="53" y="319" width="32" height="41" xoffset="4" yoffset="28" xadvance="40" page="0" chnl="0" />
|
||||||
char id=121 x=343 y=99 width=36 height=56 xoffset=-1 yoffset=28 xadvance=34 page=0 chnl=0
|
<char id="118" x="85" y="319" width="35" height="40" xoffset="0" yoffset="28" xadvance="35" page="0" chnl="0" />
|
||||||
char id=122 x=210 y=319 width=32 height=40 xoffset=2 yoffset=28 xadvance=35 page=0 chnl=0
|
<char id="119" x="120" y="319" width="54" height="40" xoffset="0" yoffset="28" xadvance="54" page="0" chnl="0" />
|
||||||
char id=123 x=113 y=0 width=24 height=73 xoffset=1 yoffset=9 xadvance=25 page=0 chnl=0
|
<char id="120" x="174" y="319" width="36" height="40" xoffset="0" yoffset="28" xadvance="36" page="0" chnl="0" />
|
||||||
char id=124 x=348 y=0 width=7 height=63 xoffset=5 yoffset=14 xadvance=17 page=0 chnl=0
|
<char id="121" x="343" y="99" width="36" height="56" xoffset="-1" yoffset="28" xadvance="34" page="0" chnl="0" />
|
||||||
char id=125 x=137 y=0 width=24 height=73 xoffset=-1 yoffset=9 xadvance=24 page=0 chnl=0
|
<char id="122" x="210" y="319" width="32" height="40" xoffset="2" yoffset="28" xadvance="35" page="0" chnl="0" />
|
||||||
char id=126 x=462 y=319 width=42 height=16 xoffset=4 yoffset=38 xadvance=50 page=0 chnl=0
|
<char id="123" x="113" y="0" width="24" height="73" xoffset="1" yoffset="9" xadvance="25" page="0" chnl="0" />
|
||||||
char id=127 x=0 y=0 width=70 height=99 xoffset=2 yoffset=-11 xadvance=74 page=0 chnl=0
|
<char id="124" x="348" y="0" width="7" height="63" xoffset="5" yoffset="14" xadvance="17" page="0" chnl="0" />
|
||||||
kernings count=382
|
<char id="125" x="137" y="0" width="24" height="73" xoffset="-1" yoffset="9" xadvance="24" page="0" chnl="0" />
|
||||||
kerning first=70 second=74 amount=-9
|
<char id="126" x="462" y="319" width="42" height="16" xoffset="4" yoffset="38" xadvance="50" page="0" chnl="0" />
|
||||||
kerning first=34 second=97 amount=-2
|
<char id="127" x="0" y="0" width="70" height="99" xoffset="2" yoffset="-11" xadvance="74" page="0" chnl="0" />
|
||||||
kerning first=34 second=101 amount=-2
|
</chars>
|
||||||
kerning first=34 second=113 amount=-2
|
<kernings count="382">
|
||||||
kerning first=34 second=99 amount=-2
|
<kerning first="70" second="74" amount="-9" />
|
||||||
kerning first=70 second=99 amount=-1
|
<kerning first="34" second="97" amount="-2" />
|
||||||
kerning first=88 second=113 amount=-1
|
<kerning first="34" second="101" amount="-2" />
|
||||||
kerning first=84 second=46 amount=-8
|
<kerning first="34" second="113" amount="-2" />
|
||||||
kerning first=84 second=119 amount=-2
|
<kerning first="34" second="99" amount="-2" />
|
||||||
kerning first=87 second=97 amount=-1
|
<kerning first="70" second="99" amount="-1" />
|
||||||
kerning first=90 second=117 amount=-1
|
<kerning first="88" second="113" amount="-1" />
|
||||||
kerning first=39 second=97 amount=-2
|
<kerning first="84" second="46" amount="-8" />
|
||||||
kerning first=69 second=111 amount=-1
|
<kerning first="84" second="119" amount="-2" />
|
||||||
kerning first=87 second=41 amount=1
|
<kerning first="87" second="97" amount="-1" />
|
||||||
kerning first=76 second=86 amount=-6
|
<kerning first="90" second="117" amount="-1" />
|
||||||
kerning first=121 second=34 amount=1
|
<kerning first="39" second="97" amount="-2" />
|
||||||
kerning first=40 second=86 amount=1
|
<kerning first="69" second="111" amount="-1" />
|
||||||
kerning first=85 second=65 amount=-1
|
<kerning first="87" second="41" amount="1" />
|
||||||
kerning first=89 second=89 amount=1
|
<kerning first="76" second="86" amount="-6" />
|
||||||
kerning first=72 second=65 amount=1
|
<kerning first="121" second="34" amount="1" />
|
||||||
kerning first=104 second=39 amount=-4
|
<kerning first="40" second="86" amount="1" />
|
||||||
kerning first=114 second=102 amount=1
|
<kerning first="85" second="65" amount="-1" />
|
||||||
kerning first=89 second=42 amount=-2
|
<kerning first="89" second="89" amount="1" />
|
||||||
kerning first=114 second=34 amount=1
|
<kerning first="72" second="65" amount="1" />
|
||||||
kerning first=84 second=115 amount=-4
|
<kerning first="104" second="39" amount="-4" />
|
||||||
kerning first=84 second=71 amount=-1
|
<kerning first="114" second="102" amount="1" />
|
||||||
kerning first=89 second=101 amount=-2
|
<kerning first="89" second="42" amount="-2" />
|
||||||
kerning first=89 second=45 amount=-2
|
<kerning first="114" second="34" amount="1" />
|
||||||
kerning first=122 second=99 amount=-1
|
<kerning first="84" second="115" amount="-4" />
|
||||||
kerning first=78 second=88 amount=1
|
<kerning first="84" second="71" amount="-1" />
|
||||||
kerning first=68 second=89 amount=-2
|
<kerning first="89" second="101" amount="-2" />
|
||||||
kerning first=122 second=103 amount=-1
|
<kerning first="89" second="45" amount="-2" />
|
||||||
kerning first=78 second=84 amount=-1
|
<kerning first="122" second="99" amount="-1" />
|
||||||
kerning first=86 second=103 amount=-2
|
<kerning first="78" second="88" amount="1" />
|
||||||
kerning first=89 second=67 amount=-1
|
<kerning first="68" second="89" amount="-2" />
|
||||||
kerning first=89 second=79 amount=-1
|
<kerning first="122" second="103" amount="-1" />
|
||||||
kerning first=75 second=111 amount=-1
|
<kerning first="78" second="84" amount="-1" />
|
||||||
kerning first=111 second=120 amount=-1
|
<kerning first="86" second="103" amount="-2" />
|
||||||
kerning first=87 second=44 amount=-4
|
<kerning first="89" second="67" amount="-1" />
|
||||||
kerning first=91 second=74 amount=-1
|
<kerning first="89" second="79" amount="-1" />
|
||||||
kerning first=120 second=111 amount=-1
|
<kerning first="75" second="111" amount="-1" />
|
||||||
kerning first=84 second=111 amount=-3
|
<kerning first="111" second="120" amount="-1" />
|
||||||
kerning first=102 second=113 amount=-1
|
<kerning first="87" second="44" amount="-4" />
|
||||||
kerning first=80 second=88 amount=-1
|
<kerning first="91" second="74" amount="-1" />
|
||||||
kerning first=66 second=84 amount=-1
|
<kerning first="120" second="111" amount="-1" />
|
||||||
kerning first=65 second=87 amount=-2
|
<kerning first="84" second="111" amount="-3" />
|
||||||
kerning first=86 second=100 amount=-2
|
<kerning first="102" second="113" amount="-1" />
|
||||||
kerning first=122 second=100 amount=-1
|
<kerning first="80" second="88" amount="-1" />
|
||||||
kerning first=75 second=118 amount=-1
|
<kerning first="66" second="84" amount="-1" />
|
||||||
kerning first=70 second=118 amount=-1
|
<kerning first="65" second="87" amount="-2" />
|
||||||
kerning first=73 second=88 amount=1
|
<kerning first="86" second="100" amount="-2" />
|
||||||
kerning first=70 second=121 amount=-1
|
<kerning first="122" second="100" amount="-1" />
|
||||||
kerning first=65 second=34 amount=-4
|
<kerning first="75" second="118" amount="-1" />
|
||||||
kerning first=39 second=101 amount=-2
|
<kerning first="70" second="118" amount="-1" />
|
||||||
kerning first=75 second=101 amount=-1
|
<kerning first="73" second="88" amount="1" />
|
||||||
kerning first=84 second=99 amount=-3
|
<kerning first="70" second="121" amount="-1" />
|
||||||
kerning first=84 second=65 amount=-3
|
<kerning first="65" second="34" amount="-4" />
|
||||||
kerning first=112 second=39 amount=-1
|
<kerning first="39" second="101" amount="-2" />
|
||||||
kerning first=76 second=39 amount=-12
|
<kerning first="75" second="101" amount="-1" />
|
||||||
kerning first=78 second=65 amount=1
|
<kerning first="84" second="99" amount="-3" />
|
||||||
kerning first=88 second=45 amount=-2
|
<kerning first="84" second="65" amount="-3" />
|
||||||
kerning first=65 second=121 amount=-2
|
<kerning first="112" second="39" amount="-1" />
|
||||||
kerning first=34 second=111 amount=-2
|
<kerning first="76" second="39" amount="-12" />
|
||||||
kerning first=89 second=85 amount=-3
|
<kerning first="78" second="65" amount="1" />
|
||||||
kerning first=114 second=99 amount=-1
|
<kerning first="88" second="45" amount="-2" />
|
||||||
kerning first=86 second=125 amount=1
|
<kerning first="65" second="121" amount="-2" />
|
||||||
kerning first=70 second=111 amount=-1
|
<kerning first="34" second="111" amount="-2" />
|
||||||
kerning first=89 second=120 amount=-1
|
<kerning first="89" second="85" amount="-3" />
|
||||||
kerning first=90 second=119 amount=-1
|
<kerning first="114" second="99" amount="-1" />
|
||||||
kerning first=120 second=99 amount=-1
|
<kerning first="86" second="125" amount="1" />
|
||||||
kerning first=89 second=117 amount=-1
|
<kerning first="70" second="111" amount="-1" />
|
||||||
kerning first=82 second=89 amount=-2
|
<kerning first="89" second="120" amount="-1" />
|
||||||
kerning first=75 second=117 amount=-1
|
<kerning first="90" second="119" amount="-1" />
|
||||||
kerning first=34 second=34 amount=-4
|
<kerning first="120" second="99" amount="-1" />
|
||||||
kerning first=89 second=110 amount=-1
|
<kerning first="89" second="117" amount="-1" />
|
||||||
kerning first=88 second=101 amount=-1
|
<kerning first="82" second="89" amount="-2" />
|
||||||
kerning first=107 second=103 amount=-1
|
<kerning first="75" second="117" amount="-1" />
|
||||||
kerning first=34 second=115 amount=-3
|
<kerning first="34" second="34" amount="-4" />
|
||||||
kerning first=98 second=39 amount=-1
|
<kerning first="89" second="110" amount="-1" />
|
||||||
kerning first=70 second=65 amount=-6
|
<kerning first="88" second="101" amount="-1" />
|
||||||
kerning first=70 second=46 amount=-8
|
<kerning first="107" second="103" amount="-1" />
|
||||||
kerning first=98 second=34 amount=-1
|
<kerning first="34" second="115" amount="-3" />
|
||||||
kerning first=70 second=84 amount=1
|
<kerning first="98" second="39" amount="-1" />
|
||||||
kerning first=114 second=100 amount=-1
|
<kerning first="70" second="65" amount="-6" />
|
||||||
kerning first=88 second=79 amount=-1
|
<kerning first="70" second="46" amount="-8" />
|
||||||
kerning first=39 second=113 amount=-2
|
<kerning first="98" second="34" amount="-1" />
|
||||||
kerning first=114 second=103 amount=-1
|
<kerning first="70" second="84" amount="1" />
|
||||||
kerning first=77 second=65 amount=1
|
<kerning first="114" second="100" amount="-1" />
|
||||||
kerning first=120 second=103 amount=-1
|
<kerning first="88" second="79" amount="-1" />
|
||||||
kerning first=114 second=121 amount=1
|
<kerning first="39" second="113" amount="-2" />
|
||||||
kerning first=89 second=100 amount=-2
|
<kerning first="114" second="103" amount="-1" />
|
||||||
kerning first=80 second=65 amount=-5
|
<kerning first="77" second="65" amount="1" />
|
||||||
kerning first=121 second=111 amount=-1
|
<kerning first="120" second="103" amount="-1" />
|
||||||
kerning first=84 second=74 amount=-8
|
<kerning first="114" second="121" amount="1" />
|
||||||
kerning first=122 second=111 amount=-1
|
<kerning first="89" second="100" amount="-2" />
|
||||||
kerning first=114 second=118 amount=1
|
<kerning first="80" second="65" amount="-5" />
|
||||||
kerning first=102 second=41 amount=1
|
<kerning first="121" second="111" amount="-1" />
|
||||||
kerning first=122 second=113 amount=-1
|
<kerning first="84" second="74" amount="-8" />
|
||||||
kerning first=89 second=122 amount=-1
|
<kerning first="122" second="111" amount="-1" />
|
||||||
kerning first=89 second=38 amount=-1
|
<kerning first="114" second="118" amount="1" />
|
||||||
kerning first=81 second=89 amount=-1
|
<kerning first="102" second="41" amount="1" />
|
||||||
kerning first=114 second=111 amount=-1
|
<kerning first="122" second="113" amount="-1" />
|
||||||
kerning first=46 second=34 amount=-6
|
<kerning first="89" second="122" amount="-1" />
|
||||||
kerning first=84 second=112 amount=-4
|
<kerning first="89" second="38" amount="-1" />
|
||||||
kerning first=112 second=34 amount=-1
|
<kerning first="81" second="89" amount="-1" />
|
||||||
kerning first=76 second=34 amount=-12
|
<kerning first="114" second="111" amount="-1" />
|
||||||
kerning first=102 second=125 amount=1
|
<kerning first="46" second="34" amount="-6" />
|
||||||
kerning first=39 second=115 amount=-3
|
<kerning first="84" second="112" amount="-4" />
|
||||||
kerning first=76 second=118 amount=-5
|
<kerning first="112" second="34" amount="-1" />
|
||||||
kerning first=86 second=99 amount=-2
|
<kerning first="76" second="34" amount="-12" />
|
||||||
kerning first=84 second=84 amount=1
|
<kerning first="102" second="125" amount="1" />
|
||||||
kerning first=86 second=65 amount=-3
|
<kerning first="39" second="115" amount="-3" />
|
||||||
kerning first=87 second=101 amount=-1
|
<kerning first="76" second="118" amount="-5" />
|
||||||
kerning first=67 second=125 amount=-1
|
<kerning first="86" second="99" amount="-2" />
|
||||||
kerning first=120 second=113 amount=-1
|
<kerning first="84" second="84" amount="1" />
|
||||||
kerning first=118 second=46 amount=-4
|
<kerning first="86" second="65" amount="-3" />
|
||||||
kerning first=88 second=103 amount=-1
|
<kerning first="87" second="101" amount="-1" />
|
||||||
kerning first=111 second=122 amount=-1
|
<kerning first="67" second="125" amount="-1" />
|
||||||
kerning first=77 second=84 amount=-1
|
<kerning first="120" second="113" amount="-1" />
|
||||||
kerning first=114 second=46 amount=-4
|
<kerning first="118" second="46" amount="-4" />
|
||||||
kerning first=34 second=39 amount=-4
|
<kerning first="88" second="103" amount="-1" />
|
||||||
kerning first=114 second=44 amount=-4
|
<kerning first="111" second="122" amount="-1" />
|
||||||
kerning first=69 second=84 amount=1
|
<kerning first="77" second="84" amount="-1" />
|
||||||
kerning first=89 second=46 amount=-7
|
<kerning first="114" second="46" amount="-4" />
|
||||||
kerning first=97 second=39 amount=-2
|
<kerning first="34" second="39" amount="-4" />
|
||||||
kerning first=34 second=100 amount=-2
|
<kerning first="114" second="44" amount="-4" />
|
||||||
kerning first=70 second=100 amount=-1
|
<kerning first="69" second="84" amount="1" />
|
||||||
kerning first=84 second=120 amount=-3
|
<kerning first="89" second="46" amount="-7" />
|
||||||
kerning first=90 second=118 amount=-1
|
<kerning first="97" second="39" amount="-2" />
|
||||||
kerning first=70 second=114 amount=-1
|
<kerning first="34" second="100" amount="-2" />
|
||||||
kerning first=34 second=112 amount=-1
|
<kerning first="70" second="100" amount="-1" />
|
||||||
kerning first=109 second=34 amount=-4
|
<kerning first="84" second="120" amount="-3" />
|
||||||
kerning first=86 second=113 amount=-2
|
<kerning first="90" second="118" amount="-1" />
|
||||||
kerning first=88 second=71 amount=-1
|
<kerning first="70" second="114" amount="-1" />
|
||||||
kerning first=66 second=89 amount=-2
|
<kerning first="34" second="112" amount="-1" />
|
||||||
kerning first=102 second=103 amount=-1
|
<kerning first="109" second="34" amount="-4" />
|
||||||
kerning first=88 second=67 amount=-1
|
<kerning first="86" second="113" amount="-2" />
|
||||||
kerning first=39 second=110 amount=-1
|
<kerning first="88" second="71" amount="-1" />
|
||||||
kerning first=75 second=110 amount=-1
|
<kerning first="66" second="89" amount="-2" />
|
||||||
kerning first=88 second=117 amount=-1
|
<kerning first="102" second="103" amount="-1" />
|
||||||
kerning first=89 second=118 amount=-1
|
<kerning first="88" second="67" amount="-1" />
|
||||||
kerning first=97 second=118 amount=-1
|
<kerning first="39" second="110" amount="-1" />
|
||||||
kerning first=87 second=65 amount=-2
|
<kerning first="75" second="110" amount="-1" />
|
||||||
kerning first=73 second=89 amount=-1
|
<kerning first="88" second="117" amount="-1" />
|
||||||
kerning first=89 second=74 amount=-3
|
<kerning first="89" second="118" amount="-1" />
|
||||||
kerning first=102 second=101 amount=-1
|
<kerning first="97" second="118" amount="-1" />
|
||||||
kerning first=86 second=111 amount=-2
|
<kerning first="87" second="65" amount="-2" />
|
||||||
kerning first=65 second=119 amount=-1
|
<kerning first="73" second="89" amount="-1" />
|
||||||
kerning first=84 second=100 amount=-3
|
<kerning first="89" second="74" amount="-3" />
|
||||||
kerning first=104 second=34 amount=-4
|
<kerning first="102" second="101" amount="-1" />
|
||||||
kerning first=86 second=41 amount=1
|
<kerning first="86" second="111" amount="-2" />
|
||||||
kerning first=111 second=34 amount=-5
|
<kerning first="65" second="119" amount="-1" />
|
||||||
kerning first=40 second=89 amount=1
|
<kerning first="84" second="100" amount="-3" />
|
||||||
kerning first=121 second=39 amount=1
|
<kerning first="104" second="34" amount="-4" />
|
||||||
kerning first=68 second=90 amount=-1
|
<kerning first="86" second="41" amount="1" />
|
||||||
kerning first=114 second=113 amount=-1
|
<kerning first="111" second="34" amount="-5" />
|
||||||
kerning first=68 second=88 amount=-1
|
<kerning first="40" second="89" amount="1" />
|
||||||
kerning first=98 second=120 amount=-1
|
<kerning first="121" second="39" amount="1" />
|
||||||
kerning first=110 second=34 amount=-4
|
<kerning first="68" second="90" amount="-1" />
|
||||||
kerning first=119 second=44 amount=-4
|
<kerning first="114" second="113" amount="-1" />
|
||||||
kerning first=119 second=46 amount=-4
|
<kerning first="68" second="88" amount="-1" />
|
||||||
kerning first=118 second=44 amount=-4
|
<kerning first="98" second="120" amount="-1" />
|
||||||
kerning first=84 second=114 amount=-3
|
<kerning first="110" second="34" amount="-4" />
|
||||||
kerning first=86 second=97 amount=-2
|
<kerning first="119" second="44" amount="-4" />
|
||||||
kerning first=68 second=86 amount=-1
|
<kerning first="119" second="46" amount="-4" />
|
||||||
kerning first=86 second=93 amount=1
|
<kerning first="118" second="44" amount="-4" />
|
||||||
kerning first=97 second=34 amount=-2
|
<kerning first="84" second="114" amount="-3" />
|
||||||
kerning first=34 second=65 amount=-4
|
<kerning first="86" second="97" amount="-2" />
|
||||||
kerning first=84 second=118 amount=-3
|
<kerning first="68" second="86" amount="-1" />
|
||||||
kerning first=76 second=84 amount=-10
|
<kerning first="86" second="93" amount="1" />
|
||||||
kerning first=107 second=99 amount=-1
|
<kerning first="97" second="34" amount="-2" />
|
||||||
kerning first=121 second=46 amount=-4
|
<kerning first="34" second="65" amount="-4" />
|
||||||
kerning first=123 second=85 amount=-1
|
<kerning first="84" second="118" amount="-3" />
|
||||||
kerning first=65 second=63 amount=-2
|
<kerning first="76" second="84" amount="-10" />
|
||||||
kerning first=89 second=44 amount=-7
|
<kerning first="107" second="99" amount="-1" />
|
||||||
kerning first=80 second=118 amount=1
|
<kerning first="121" second="46" amount="-4" />
|
||||||
kerning first=112 second=122 amount=-1
|
<kerning first="123" second="85" amount="-1" />
|
||||||
kerning first=79 second=65 amount=-1
|
<kerning first="65" second="63" amount="-2" />
|
||||||
kerning first=80 second=121 amount=1
|
<kerning first="89" second="44" amount="-7" />
|
||||||
kerning first=118 second=34 amount=1
|
<kerning first="80" second="118" amount="1" />
|
||||||
kerning first=87 second=45 amount=-2
|
<kerning first="112" second="122" amount="-1" />
|
||||||
kerning first=69 second=100 amount=-1
|
<kerning first="79" second="65" amount="-1" />
|
||||||
kerning first=87 second=103 amount=-1
|
<kerning first="80" second="121" amount="1" />
|
||||||
kerning first=112 second=120 amount=-1
|
<kerning first="118" second="34" amount="1" />
|
||||||
kerning first=68 second=44 amount=-4
|
<kerning first="87" second="45" amount="-2" />
|
||||||
kerning first=86 second=45 amount=-1
|
<kerning first="69" second="100" amount="-1" />
|
||||||
kerning first=39 second=34 amount=-4
|
<kerning first="87" second="103" amount="-1" />
|
||||||
kerning first=68 second=46 amount=-4
|
<kerning first="112" second="120" amount="-1" />
|
||||||
kerning first=65 second=89 amount=-3
|
<kerning first="68" second="44" amount="-4" />
|
||||||
kerning first=69 second=118 amount=-1
|
<kerning first="86" second="45" amount="-1" />
|
||||||
kerning first=88 second=99 amount=-1
|
<kerning first="39" second="34" amount="-4" />
|
||||||
kerning first=87 second=46 amount=-4
|
<kerning first="68" second="46" amount="-4" />
|
||||||
kerning first=47 second=47 amount=-8
|
<kerning first="65" second="89" amount="-3" />
|
||||||
kerning first=73 second=65 amount=1
|
<kerning first="69" second="118" amount="-1" />
|
||||||
kerning first=123 second=74 amount=-1
|
<kerning first="88" second="99" amount="-1" />
|
||||||
kerning first=69 second=102 amount=-1
|
<kerning first="87" second="46" amount="-4" />
|
||||||
kerning first=87 second=111 amount=-1
|
<kerning first="47" second="47" amount="-8" />
|
||||||
kerning first=39 second=112 amount=-1
|
<kerning first="73" second="65" amount="1" />
|
||||||
kerning first=89 second=116 amount=-1
|
<kerning first="123" second="74" amount="-1" />
|
||||||
kerning first=70 second=113 amount=-1
|
<kerning first="69" second="102" amount="-1" />
|
||||||
kerning first=77 second=88 amount=1
|
<kerning first="87" second="111" amount="-1" />
|
||||||
kerning first=84 second=32 amount=-1
|
<kerning first="39" second="112" amount="-1" />
|
||||||
kerning first=90 second=103 amount=-1
|
<kerning first="89" second="116" amount="-1" />
|
||||||
kerning first=65 second=86 amount=-3
|
<kerning first="70" second="113" amount="-1" />
|
||||||
kerning first=75 second=112 amount=-1
|
<kerning first="77" second="88" amount="1" />
|
||||||
kerning first=39 second=109 amount=-1
|
<kerning first="84" second="32" amount="-1" />
|
||||||
kerning first=75 second=81 amount=-1
|
<kerning first="90" second="103" amount="-1" />
|
||||||
kerning first=89 second=115 amount=-2
|
<kerning first="65" second="86" amount="-3" />
|
||||||
kerning first=84 second=83 amount=-1
|
<kerning first="75" second="112" amount="-1" />
|
||||||
kerning first=89 second=87 amount=1
|
<kerning first="39" second="109" amount="-1" />
|
||||||
kerning first=114 second=101 amount=-1
|
<kerning first="75" second="81" amount="-1" />
|
||||||
kerning first=116 second=111 amount=-1
|
<kerning first="89" second="115" amount="-2" />
|
||||||
kerning first=90 second=100 amount=-1
|
<kerning first="84" second="83" amount="-1" />
|
||||||
kerning first=84 second=122 amount=-2
|
<kerning first="89" second="87" amount="1" />
|
||||||
kerning first=68 second=84 amount=-1
|
<kerning first="114" second="101" amount="-1" />
|
||||||
kerning first=32 second=84 amount=-1
|
<kerning first="116" second="111" amount="-1" />
|
||||||
kerning first=84 second=117 amount=-3
|
<kerning first="90" second="100" amount="-1" />
|
||||||
kerning first=74 second=65 amount=-1
|
<kerning first="84" second="122" amount="-2" />
|
||||||
kerning first=107 second=101 amount=-1
|
<kerning first="68" second="84" amount="-1" />
|
||||||
kerning first=75 second=109 amount=-1
|
<kerning first="32" second="84" amount="-1" />
|
||||||
kerning first=80 second=46 amount=-11
|
<kerning first="84" second="117" amount="-3" />
|
||||||
kerning first=89 second=93 amount=1
|
<kerning first="74" second="65" amount="-1" />
|
||||||
kerning first=89 second=65 amount=-3
|
<kerning first="107" second="101" amount="-1" />
|
||||||
kerning first=87 second=117 amount=-1
|
<kerning first="75" second="109" amount="-1" />
|
||||||
kerning first=89 second=81 amount=-1
|
<kerning first="80" second="46" amount="-11" />
|
||||||
kerning first=39 second=103 amount=-2
|
<kerning first="89" second="93" amount="1" />
|
||||||
kerning first=86 second=101 amount=-2
|
<kerning first="89" second="65" amount="-3" />
|
||||||
kerning first=86 second=117 amount=-1
|
<kerning first="87" second="117" amount="-1" />
|
||||||
kerning first=84 second=113 amount=-3
|
<kerning first="89" second="81" amount="-1" />
|
||||||
kerning first=34 second=110 amount=-1
|
<kerning first="39" second="103" amount="-2" />
|
||||||
kerning first=89 second=84 amount=1
|
<kerning first="86" second="101" amount="-2" />
|
||||||
kerning first=84 second=110 amount=-4
|
<kerning first="86" second="117" amount="-1" />
|
||||||
kerning first=39 second=99 amount=-2
|
<kerning first="84" second="113" amount="-3" />
|
||||||
kerning first=88 second=121 amount=-1
|
<kerning first="34" second="110" amount="-1" />
|
||||||
kerning first=65 second=39 amount=-4
|
<kerning first="89" second="84" amount="1" />
|
||||||
kerning first=110 second=39 amount=-4
|
<kerning first="84" second="110" amount="-4" />
|
||||||
kerning first=75 second=67 amount=-1
|
<kerning first="39" second="99" amount="-2" />
|
||||||
kerning first=88 second=118 amount=-1
|
<kerning first="88" second="121" amount="-1" />
|
||||||
kerning first=86 second=114 amount=-1
|
<kerning first="65" second="39" amount="-4" />
|
||||||
kerning first=80 second=74 amount=-7
|
<kerning first="110" second="39" amount="-4" />
|
||||||
kerning first=84 second=97 amount=-4
|
<kerning first="75" second="67" amount="-1" />
|
||||||
kerning first=82 second=84 amount=-3
|
<kerning first="88" second="118" amount="-1" />
|
||||||
kerning first=91 second=85 amount=-1
|
<kerning first="86" second="114" amount="-1" />
|
||||||
kerning first=102 second=99 amount=-1
|
<kerning first="80" second="74" amount="-7" />
|
||||||
kerning first=66 second=86 amount=-1
|
<kerning first="84" second="97" amount="-4" />
|
||||||
kerning first=120 second=101 amount=-1
|
<kerning first="82" second="84" amount="-3" />
|
||||||
kerning first=102 second=93 amount=1
|
<kerning first="91" second="85" amount="-1" />
|
||||||
kerning first=75 second=100 amount=-1
|
<kerning first="102" second="99" amount="-1" />
|
||||||
kerning first=84 second=79 amount=-1
|
<kerning first="66" second="86" amount="-1" />
|
||||||
kerning first=111 second=121 amount=-1
|
<kerning first="120" second="101" amount="-1" />
|
||||||
kerning first=75 second=121 amount=-1
|
<kerning first="102" second="93" amount="1" />
|
||||||
kerning first=81 second=87 amount=-1
|
<kerning first="75" second="100" amount="-1" />
|
||||||
kerning first=107 second=113 amount=-1
|
<kerning first="84" second="79" amount="-1" />
|
||||||
kerning first=120 second=100 amount=-1
|
<kerning first="111" second="121" amount="-1" />
|
||||||
kerning first=90 second=79 amount=-1
|
<kerning first="75" second="121" amount="-1" />
|
||||||
kerning first=89 second=114 amount=-1
|
<kerning first="81" second="87" amount="-1" />
|
||||||
kerning first=122 second=101 amount=-1
|
<kerning first="107" second="113" amount="-1" />
|
||||||
kerning first=111 second=118 amount=-1
|
<kerning first="120" second="100" amount="-1" />
|
||||||
kerning first=82 second=86 amount=-1
|
<kerning first="90" second="79" amount="-1" />
|
||||||
kerning first=67 second=84 amount=-1
|
<kerning first="89" second="114" amount="-1" />
|
||||||
kerning first=70 second=101 amount=-1
|
<kerning first="122" second="101" amount="-1" />
|
||||||
kerning first=89 second=83 amount=-1
|
<kerning first="111" second="118" amount="-1" />
|
||||||
kerning first=114 second=97 amount=-1
|
<kerning first="82" second="86" amount="-1" />
|
||||||
kerning first=70 second=97 amount=-1
|
<kerning first="67" second="84" amount="-1" />
|
||||||
kerning first=89 second=102 amount=-1
|
<kerning first="70" second="101" amount="-1" />
|
||||||
kerning first=78 second=89 amount=-1
|
<kerning first="89" second="83" amount="-1" />
|
||||||
kerning first=70 second=44 amount=-8
|
<kerning first="114" second="97" amount="-1" />
|
||||||
kerning first=44 second=39 amount=-6
|
<kerning first="70" second="97" amount="-1" />
|
||||||
kerning first=84 second=45 amount=-8
|
<kerning first="89" second="102" amount="-1" />
|
||||||
kerning first=89 second=121 amount=-1
|
<kerning first="78" second="89" amount="-1" />
|
||||||
kerning first=84 second=86 amount=1
|
<kerning first="70" second="44" amount="-8" />
|
||||||
kerning first=87 second=99 amount=-1
|
<kerning first="44" second="39" amount="-6" />
|
||||||
kerning first=98 second=122 amount=-1
|
<kerning first="84" second="45" amount="-8" />
|
||||||
kerning first=89 second=112 amount=-1
|
<kerning first="89" second="121" amount="-1" />
|
||||||
kerning first=89 second=103 amount=-2
|
<kerning first="84" second="86" amount="1" />
|
||||||
kerning first=88 second=81 amount=-1
|
<kerning first="87" second="99" amount="-1" />
|
||||||
kerning first=102 second=34 amount=1
|
<kerning first="98" second="122" amount="-1" />
|
||||||
kerning first=109 second=39 amount=-4
|
<kerning first="89" second="112" amount="-1" />
|
||||||
kerning first=81 second=84 amount=-2
|
<kerning first="89" second="103" amount="-2" />
|
||||||
kerning first=121 second=97 amount=-1
|
<kerning first="88" second="81" amount="-1" />
|
||||||
kerning first=89 second=99 amount=-2
|
<kerning first="102" second="34" amount="1" />
|
||||||
kerning first=89 second=125 amount=1
|
<kerning first="109" second="39" amount="-4" />
|
||||||
kerning first=81 second=86 amount=-1
|
<kerning first="81" second="84" amount="-2" />
|
||||||
kerning first=114 second=116 amount=2
|
<kerning first="121" second="97" amount="-1" />
|
||||||
kerning first=114 second=119 amount=1
|
<kerning first="89" second="99" amount="-2" />
|
||||||
kerning first=84 second=44 amount=-8
|
<kerning first="89" second="125" amount="1" />
|
||||||
kerning first=102 second=39 amount=1
|
<kerning first="81" second="86" amount="-1" />
|
||||||
kerning first=44 second=34 amount=-6
|
<kerning first="114" second="116" amount="2" />
|
||||||
kerning first=34 second=109 amount=-1
|
<kerning first="114" second="119" amount="1" />
|
||||||
kerning first=75 second=119 amount=-2
|
<kerning first="84" second="44" amount="-8" />
|
||||||
kerning first=76 second=65 amount=1
|
<kerning first="102" second="39" amount="1" />
|
||||||
kerning first=84 second=81 amount=-1
|
<kerning first="44" second="34" amount="-6" />
|
||||||
kerning first=76 second=121 amount=-5
|
<kerning first="34" second="109" amount="-1" />
|
||||||
kerning first=69 second=101 amount=-1
|
<kerning first="75" second="119" amount="-2" />
|
||||||
kerning first=89 second=111 amount=-2
|
<kerning first="76" second="65" amount="1" />
|
||||||
kerning first=80 second=90 amount=-1
|
<kerning first="84" second="81" amount="-1" />
|
||||||
kerning first=89 second=97 amount=-3
|
<kerning first="76" second="121" amount="-5" />
|
||||||
kerning first=89 second=109 amount=-1
|
<kerning first="69" second="101" amount="-1" />
|
||||||
kerning first=90 second=99 amount=-1
|
<kerning first="89" second="111" amount="-2" />
|
||||||
kerning first=89 second=86 amount=1
|
<kerning first="80" second="90" amount="-1" />
|
||||||
kerning first=79 second=88 amount=-1
|
<kerning first="89" second="97" amount="-3" />
|
||||||
kerning first=70 second=103 amount=-1
|
<kerning first="89" second="109" amount="-1" />
|
||||||
kerning first=34 second=103 amount=-2
|
<kerning first="90" second="99" amount="-1" />
|
||||||
kerning first=84 second=67 amount=-1
|
<kerning first="89" second="86" amount="1" />
|
||||||
kerning first=76 second=79 amount=-2
|
<kerning first="79" second="88" amount="-1" />
|
||||||
kerning first=89 second=41 amount=1
|
<kerning first="70" second="103" amount="-1" />
|
||||||
kerning first=65 second=118 amount=-2
|
<kerning first="34" second="103" amount="-2" />
|
||||||
kerning first=75 second=71 amount=-1
|
<kerning first="84" second="67" amount="-1" />
|
||||||
kerning first=76 second=87 amount=-5
|
<kerning first="76" second="79" amount="-2" />
|
||||||
kerning first=77 second=89 amount=-1
|
<kerning first="89" second="41" amount="1" />
|
||||||
kerning first=90 second=113 amount=-1
|
<kerning first="65" second="118" amount="-2" />
|
||||||
kerning first=79 second=89 amount=-2
|
<kerning first="75" second="71" amount="-1" />
|
||||||
kerning first=118 second=111 amount=-1
|
<kerning first="76" second="87" amount="-5" />
|
||||||
kerning first=118 second=97 amount=-1
|
<kerning first="77" second="89" amount="-1" />
|
||||||
kerning first=88 second=100 amount=-1
|
<kerning first="90" second="113" amount="-1" />
|
||||||
kerning first=90 second=121 amount=-1
|
<kerning first="79" second="89" amount="-2" />
|
||||||
kerning first=89 second=113 amount=-2
|
<kerning first="118" second="111" amount="-1" />
|
||||||
kerning first=84 second=87 amount=1
|
<kerning first="118" second="97" amount="-1" />
|
||||||
kerning first=39 second=111 amount=-2
|
<kerning first="88" second="100" amount="-1" />
|
||||||
kerning first=80 second=44 amount=-11
|
<kerning first="90" second="121" amount="-1" />
|
||||||
kerning first=39 second=100 amount=-2
|
<kerning first="89" second="113" amount="-2" />
|
||||||
kerning first=75 second=113 amount=-1
|
<kerning first="84" second="87" amount="1" />
|
||||||
kerning first=88 second=111 amount=-1
|
<kerning first="39" second="111" amount="-2" />
|
||||||
kerning first=84 second=89 amount=1
|
<kerning first="80" second="44" amount="-11" />
|
||||||
kerning first=84 second=103 amount=-3
|
<kerning first="39" second="100" amount="-2" />
|
||||||
kerning first=70 second=117 amount=-1
|
<kerning first="75" second="113" amount="-1" />
|
||||||
kerning first=67 second=41 amount=-1
|
<kerning first="88" second="111" amount="-1" />
|
||||||
kerning first=89 second=71 amount=-1
|
<kerning first="84" second="89" amount="1" />
|
||||||
kerning first=121 second=44 amount=-4
|
<kerning first="84" second="103" amount="-3" />
|
||||||
kerning first=97 second=121 amount=-1
|
<kerning first="70" second="117" amount="-1" />
|
||||||
kerning first=87 second=113 amount=-1
|
<kerning first="67" second="41" amount="-1" />
|
||||||
kerning first=73 second=84 amount=-1
|
<kerning first="89" second="71" amount="-1" />
|
||||||
kerning first=84 second=101 amount=-3
|
<kerning first="121" second="44" amount="-4" />
|
||||||
kerning first=75 second=99 amount=-1
|
<kerning first="97" second="121" amount="-1" />
|
||||||
kerning first=65 second=85 amount=-1
|
<kerning first="87" second="113" amount="-1" />
|
||||||
kerning first=76 second=67 amount=-2
|
<kerning first="73" second="84" amount="-1" />
|
||||||
kerning first=76 second=81 amount=-2
|
<kerning first="84" second="101" amount="-3" />
|
||||||
kerning first=75 second=79 amount=-1
|
<kerning first="75" second="99" amount="-1" />
|
||||||
kerning first=39 second=65 amount=-4
|
<kerning first="65" second="85" amount="-1" />
|
||||||
kerning first=76 second=117 amount=-2
|
<kerning first="76" second="67" amount="-2" />
|
||||||
kerning first=65 second=84 amount=-5
|
<kerning first="76" second="81" amount="-2" />
|
||||||
kerning first=90 second=101 amount=-1
|
<kerning first="75" second="79" amount="-1" />
|
||||||
kerning first=84 second=121 amount=-3
|
<kerning first="39" second="65" amount="-4" />
|
||||||
kerning first=69 second=99 amount=-1
|
<kerning first="76" second="117" amount="-2" />
|
||||||
kerning first=114 second=39 amount=1
|
<kerning first="65" second="84" amount="-5" />
|
||||||
kerning first=84 second=109 amount=-4
|
<kerning first="90" second="101" amount="-1" />
|
||||||
kerning first=76 second=119 amount=-3
|
<kerning first="84" second="121" amount="-3" />
|
||||||
kerning first=76 second=85 amount=-2
|
<kerning first="69" second="99" amount="-1" />
|
||||||
kerning first=65 second=116 amount=-1
|
<kerning first="114" second="39" amount="1" />
|
||||||
kerning first=76 second=71 amount=-2
|
<kerning first="84" second="109" amount="-4" />
|
||||||
kerning first=79 second=90 amount=-1
|
<kerning first="76" second="119" amount="-3" />
|
||||||
kerning first=107 second=100 amount=-1
|
<kerning first="76" second="85" amount="-2" />
|
||||||
kerning first=90 second=111 amount=-1
|
<kerning first="65" second="116" amount="-1" />
|
||||||
kerning first=79 second=44 amount=-4
|
<kerning first="76" second="71" amount="-2" />
|
||||||
kerning first=75 second=45 amount=-2
|
<kerning first="79" second="90" amount="-1" />
|
||||||
kerning first=40 second=87 amount=1
|
<kerning first="107" second="100" amount="-1" />
|
||||||
kerning first=79 second=86 amount=-1
|
<kerning first="90" second="111" amount="-1" />
|
||||||
kerning first=102 second=100 amount=-1
|
<kerning first="79" second="44" amount="-4" />
|
||||||
kerning first=72 second=89 amount=-1
|
<kerning first="75" second="45" amount="-2" />
|
||||||
kerning first=72 second=88 amount=1
|
<kerning first="40" second="87" amount="1" />
|
||||||
kerning first=79 second=46 amount=-4
|
<kerning first="79" second="86" amount="-1" />
|
||||||
kerning first=76 second=89 amount=-8
|
<kerning first="102" second="100" amount="-1" />
|
||||||
kerning first=68 second=65 amount=-1
|
<kerning first="72" second="89" amount="-1" />
|
||||||
kerning first=79 second=84 amount=-1
|
<kerning first="72" second="88" amount="1" />
|
||||||
kerning first=87 second=100 amount=-1
|
<kerning first="79" second="46" amount="-4" />
|
||||||
kerning first=75 second=103 amount=-1
|
<kerning first="76" second="89" amount="-8" />
|
||||||
kerning first=90 second=67 amount=-1
|
<kerning first="68" second="65" amount="-1" />
|
||||||
kerning first=69 second=103 amount=-1
|
<kerning first="79" second="84" amount="-1" />
|
||||||
kerning first=90 second=71 amount=-1
|
<kerning first="87" second="100" amount="-1" />
|
||||||
kerning first=86 second=44 amount=-8
|
<kerning first="75" second="103" amount="-1" />
|
||||||
kerning first=69 second=121 amount=-1
|
<kerning first="90" second="67" amount="-1" />
|
||||||
kerning first=87 second=114 amount=-1
|
<kerning first="69" second="103" amount="-1" />
|
||||||
kerning first=118 second=39 amount=1
|
<kerning first="90" second="71" amount="-1" />
|
||||||
kerning first=46 second=39 amount=-6
|
<kerning first="86" second="44" amount="-8" />
|
||||||
kerning first=72 second=84 amount=-1
|
<kerning first="69" second="121" amount="-1" />
|
||||||
kerning first=86 second=46 amount=-8
|
<kerning first="87" second="114" amount="-1" />
|
||||||
kerning first=69 second=113 amount=-1
|
<kerning first="118" second="39" amount="1" />
|
||||||
kerning first=69 second=119 amount=-1
|
<kerning first="46" second="39" amount="-6" />
|
||||||
kerning first=39 second=39 amount=-4
|
<kerning first="72" second="84" amount="-1" />
|
||||||
kerning first=69 second=117 amount=-1
|
<kerning first="86" second="46" amount="-8" />
|
||||||
kerning first=111 second=39 amount=-5
|
<kerning first="69" second="113" amount="-1" />
|
||||||
kerning first=90 second=81 amount=-1
|
<kerning first="69" second="119" amount="-1" />
|
||||||
|
<kerning first="39" second="39" amount="-4" />
|
||||||
|
<kerning first="69" second="117" amount="-1" />
|
||||||
|
<kerning first="111" second="39" amount="-5" />
|
||||||
|
<kerning first="90" second="81" amount="-1" />
|
||||||
|
</kernings>
|
||||||
|
</font>
|
||||||
|
|||||||
@ -1,488 +1,494 @@
|
|||||||
info face="Roboto Black" size=72 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=1,1,1,1 spacing=-2,-2
|
<?xml version="1.0"?>
|
||||||
common lineHeight=85 base=67 scaleW=512 scaleH=512 pages=1 packed=0
|
<font>
|
||||||
page id=0 file="RobotoBlack72White.png"
|
<info face="Roboto Black" size="72" bold="0" italic="0" charset="" unicode="0" stretchH="100" smooth="1" aa="1" padding="1,1,1,1" spacing="-2,-2" outline="0" />
|
||||||
chars count=98
|
<common lineHeight="85" base="67" scaleW="512" scaleH="512" pages="1" packed="0" alphaChnl="0" redChnl="0" greenChnl="0" blueChnl="0" />
|
||||||
char id=0 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=66 xadvance=0 page=0 chnl=0
|
<pages>
|
||||||
char id=10 x=0 y=0 width=70 height=99 xoffset=2 yoffset=-11 xadvance=74 page=0 chnl=0
|
<page id="0" file="RobotoBlack72White.png" /> </pages>
|
||||||
char id=32 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=66 xadvance=18 page=0 chnl=0
|
<chars count="98">
|
||||||
char id=33 x=460 y=156 width=15 height=55 xoffset=3 yoffset=14 xadvance=20 page=0 chnl=0
|
<char id="0" x="0" y="0" width="0" height="0" xoffset="-1" yoffset="66" xadvance="0" page="0" chnl="0" />
|
||||||
char id=34 x=207 y=362 width=22 height=22 xoffset=0 yoffset=12 xadvance=23 page=0 chnl=0
|
<char id="10" x="0" y="0" width="70" height="99" xoffset="2" yoffset="-11" xadvance="74" page="0" chnl="0" />
|
||||||
char id=35 x=404 y=266 width=41 height=54 xoffset=0 yoffset=14 xadvance=42 page=0 chnl=0
|
<char id="32" x="0" y="0" width="0" height="0" xoffset="-1" yoffset="66" xadvance="18" page="0" chnl="0" />
|
||||||
char id=36 x=220 y=0 width=38 height=69 xoffset=2 yoffset=7 xadvance=42 page=0 chnl=0
|
<char id="33" x="460" y="156" width="15" height="55" xoffset="3" yoffset="14" xadvance="20" page="0" chnl="0" />
|
||||||
char id=37 x=167 y=156 width=49 height=56 xoffset=2 yoffset=13 xadvance=53 page=0 chnl=0
|
<char id="34" x="207" y="362" width="22" height="22" xoffset="0" yoffset="12" xadvance="23" page="0" chnl="0" />
|
||||||
char id=38 x=216 y=156 width=48 height=56 xoffset=1 yoffset=13 xadvance=48 page=0 chnl=0
|
<char id="35" x="404" y="266" width="41" height="54" xoffset="0" yoffset="14" xadvance="42" page="0" chnl="0" />
|
||||||
char id=39 x=499 y=320 width=10 height=22 xoffset=1 yoffset=12 xadvance=11 page=0 chnl=0
|
<char id="36" x="220" y="0" width="38" height="69" xoffset="2" yoffset="7" xadvance="42" page="0" chnl="0" />
|
||||||
char id=40 x=70 y=0 width=22 height=75 xoffset=3 yoffset=9 xadvance=25 page=0 chnl=0
|
<char id="37" x="167" y="156" width="49" height="56" xoffset="2" yoffset="13" xadvance="53" page="0" chnl="0" />
|
||||||
char id=41 x=92 y=0 width=23 height=75 xoffset=0 yoffset=9 xadvance=25 page=0 chnl=0
|
<char id="38" x="216" y="156" width="48" height="56" xoffset="1" yoffset="13" xadvance="48" page="0" chnl="0" />
|
||||||
char id=42 x=103 y=362 width=36 height=34 xoffset=-1 yoffset=14 xadvance=33 page=0 chnl=0
|
<char id="39" x="499" y="320" width="10" height="22" xoffset="1" yoffset="12" xadvance="11" page="0" chnl="0" />
|
||||||
char id=43 x=0 y=362 width=37 height=40 xoffset=1 yoffset=23 xadvance=39 page=0 chnl=0
|
<char id="40" x="70" y="0" width="22" height="75" xoffset="3" yoffset="9" xadvance="25" page="0" chnl="0" />
|
||||||
char id=44 x=483 y=320 width=16 height=25 xoffset=0 yoffset=57 xadvance=20 page=0 chnl=0
|
<char id="41" x="92" y="0" width="23" height="75" xoffset="0" yoffset="9" xadvance="25" page="0" chnl="0" />
|
||||||
char id=45 x=308 y=362 width=23 height=12 xoffset=4 yoffset=38 xadvance=32 page=0 chnl=0
|
<char id="42" x="103" y="362" width="36" height="34" xoffset="-1" yoffset="14" xadvance="33" page="0" chnl="0" />
|
||||||
char id=46 x=270 y=362 width=15 height=15 xoffset=3 yoffset=54 xadvance=22 page=0 chnl=0
|
<char id="43" x="0" y="362" width="37" height="40" xoffset="1" yoffset="23" xadvance="39" page="0" chnl="0" />
|
||||||
char id=47 x=374 y=0 width=29 height=58 xoffset=-3 yoffset=14 xadvance=25 page=0 chnl=0
|
<char id="44" x="483" y="320" width="16" height="25" xoffset="0" yoffset="57" xadvance="20" page="0" chnl="0" />
|
||||||
char id=48 x=77 y=156 width=38 height=56 xoffset=2 yoffset=13 xadvance=42 page=0 chnl=0
|
<char id="45" x="308" y="362" width="23" height="12" xoffset="4" yoffset="38" xadvance="32" page="0" chnl="0" />
|
||||||
char id=49 x=299 y=266 width=26 height=54 xoffset=4 yoffset=14 xadvance=41 page=0 chnl=0
|
<char id="46" x="270" y="362" width="15" height="15" xoffset="3" yoffset="54" xadvance="22" page="0" chnl="0" />
|
||||||
char id=50 x=383 y=156 width=39 height=55 xoffset=1 yoffset=13 xadvance=42 page=0 chnl=0
|
<char id="47" x="374" y="0" width="29" height="58" xoffset="-3" yoffset="14" xadvance="25" page="0" chnl="0" />
|
||||||
char id=51 x=434 y=99 width=39 height=56 xoffset=1 yoffset=13 xadvance=42 page=0 chnl=0
|
<char id="48" x="77" y="156" width="38" height="56" xoffset="2" yoffset="13" xadvance="42" page="0" chnl="0" />
|
||||||
char id=52 x=325 y=266 width=40 height=54 xoffset=1 yoffset=14 xadvance=42 page=0 chnl=0
|
<char id="49" x="299" y="266" width="26" height="54" xoffset="4" yoffset="14" xadvance="41" page="0" chnl="0" />
|
||||||
char id=53 x=422 y=156 width=38 height=55 xoffset=2 yoffset=14 xadvance=42 page=0 chnl=0
|
<char id="50" x="383" y="156" width="39" height="55" xoffset="1" yoffset="13" xadvance="42" page="0" chnl="0" />
|
||||||
char id=54 x=0 y=156 width=39 height=56 xoffset=2 yoffset=13 xadvance=42 page=0 chnl=0
|
<char id="51" x="434" y="99" width="39" height="56" xoffset="1" yoffset="13" xadvance="42" page="0" chnl="0" />
|
||||||
char id=55 x=365 y=266 width=39 height=54 xoffset=1 yoffset=14 xadvance=42 page=0 chnl=0
|
<char id="52" x="325" y="266" width="40" height="54" xoffset="1" yoffset="14" xadvance="42" page="0" chnl="0" />
|
||||||
char id=56 x=473 y=99 width=38 height=56 xoffset=2 yoffset=13 xadvance=42 page=0 chnl=0
|
<char id="53" x="422" y="156" width="38" height="55" xoffset="2" yoffset="14" xadvance="42" page="0" chnl="0" />
|
||||||
char id=57 x=39 y=156 width=38 height=56 xoffset=2 yoffset=13 xadvance=42 page=0 chnl=0
|
<char id="54" x="0" y="156" width="39" height="56" xoffset="2" yoffset="13" xadvance="42" page="0" chnl="0" />
|
||||||
char id=58 x=471 y=266 width=15 height=43 xoffset=3 yoffset=26 xadvance=21 page=0 chnl=0
|
<char id="55" x="365" y="266" width="39" height="54" xoffset="1" yoffset="14" xadvance="42" page="0" chnl="0" />
|
||||||
char id=59 x=150 y=156 width=17 height=56 xoffset=1 yoffset=26 xadvance=21 page=0 chnl=0
|
<char id="56" x="473" y="99" width="38" height="56" xoffset="2" yoffset="13" xadvance="42" page="0" chnl="0" />
|
||||||
char id=60 x=37 y=362 width=33 height=38 xoffset=1 yoffset=26 xadvance=37 page=0 chnl=0
|
<char id="57" x="39" y="156" width="38" height="56" xoffset="2" yoffset="13" xadvance="42" page="0" chnl="0" />
|
||||||
char id=61 x=172 y=362 width=35 height=27 xoffset=3 yoffset=31 xadvance=42 page=0 chnl=0
|
<char id="58" x="471" y="266" width="15" height="43" xoffset="3" yoffset="26" xadvance="21" page="0" chnl="0" />
|
||||||
char id=62 x=70 y=362 width=33 height=38 xoffset=3 yoffset=26 xadvance=37 page=0 chnl=0
|
<char id="59" x="150" y="156" width="17" height="56" xoffset="1" yoffset="26" xadvance="21" page="0" chnl="0" />
|
||||||
char id=63 x=115 y=156 width=35 height=56 xoffset=0 yoffset=13 xadvance=36 page=0 chnl=0
|
<char id="60" x="37" y="362" width="33" height="38" xoffset="1" yoffset="26" xadvance="37" page="0" chnl="0" />
|
||||||
char id=64 x=258 y=0 width=61 height=68 xoffset=1 yoffset=16 xadvance=64 page=0 chnl=0
|
<char id="61" x="172" y="362" width="35" height="27" xoffset="3" yoffset="31" xadvance="42" page="0" chnl="0" />
|
||||||
char id=65 x=0 y=212 width=53 height=54 xoffset=-2 yoffset=14 xadvance=49 page=0 chnl=0
|
<char id="62" x="70" y="362" width="33" height="38" xoffset="3" yoffset="26" xadvance="37" page="0" chnl="0" />
|
||||||
char id=66 x=53 y=212 width=42 height=54 xoffset=3 yoffset=14 xadvance=47 page=0 chnl=0
|
<char id="63" x="115" y="156" width="35" height="56" xoffset="0" yoffset="13" xadvance="36" page="0" chnl="0" />
|
||||||
char id=67 x=37 y=99 width=46 height=56 xoffset=1 yoffset=13 xadvance=47 page=0 chnl=0
|
<char id="64" x="258" y="0" width="61" height="68" xoffset="1" yoffset="16" xadvance="64" page="0" chnl="0" />
|
||||||
char id=68 x=95 y=212 width=42 height=54 xoffset=3 yoffset=14 xadvance=47 page=0 chnl=0
|
<char id="65" x="0" y="212" width="53" height="54" xoffset="-2" yoffset="14" xadvance="49" page="0" chnl="0" />
|
||||||
char id=69 x=137 y=212 width=38 height=54 xoffset=3 yoffset=14 xadvance=41 page=0 chnl=0
|
<char id="66" x="53" y="212" width="42" height="54" xoffset="3" yoffset="14" xadvance="47" page="0" chnl="0" />
|
||||||
char id=70 x=475 y=156 width=36 height=54 xoffset=3 yoffset=14 xadvance=39 page=0 chnl=0
|
<char id="67" x="37" y="99" width="46" height="56" xoffset="1" yoffset="13" xadvance="47" page="0" chnl="0" />
|
||||||
char id=71 x=83 y=99 width=45 height=56 xoffset=2 yoffset=13 xadvance=49 page=0 chnl=0
|
<char id="68" x="95" y="212" width="42" height="54" xoffset="3" yoffset="14" xadvance="47" page="0" chnl="0" />
|
||||||
char id=72 x=175 y=212 width=45 height=54 xoffset=3 yoffset=14 xadvance=51 page=0 chnl=0
|
<char id="69" x="137" y="212" width="38" height="54" xoffset="3" yoffset="14" xadvance="41" page="0" chnl="0" />
|
||||||
char id=73 x=220 y=212 width=14 height=54 xoffset=4 yoffset=14 xadvance=22 page=0 chnl=0
|
<char id="70" x="475" y="156" width="36" height="54" xoffset="3" yoffset="14" xadvance="39" page="0" chnl="0" />
|
||||||
char id=74 x=264 y=156 width=37 height=55 xoffset=0 yoffset=14 xadvance=40 page=0 chnl=0
|
<char id="71" x="83" y="99" width="45" height="56" xoffset="2" yoffset="13" xadvance="49" page="0" chnl="0" />
|
||||||
char id=75 x=234 y=212 width=45 height=54 xoffset=3 yoffset=14 xadvance=46 page=0 chnl=0
|
<char id="72" x="175" y="212" width="45" height="54" xoffset="3" yoffset="14" xadvance="51" page="0" chnl="0" />
|
||||||
char id=76 x=279 y=212 width=36 height=54 xoffset=3 yoffset=14 xadvance=39 page=0 chnl=0
|
<char id="73" x="220" y="212" width="14" height="54" xoffset="4" yoffset="14" xadvance="22" page="0" chnl="0" />
|
||||||
char id=77 x=315 y=212 width=58 height=54 xoffset=3 yoffset=14 xadvance=63 page=0 chnl=0
|
<char id="74" x="264" y="156" width="37" height="55" xoffset="0" yoffset="14" xadvance="40" page="0" chnl="0" />
|
||||||
char id=78 x=373 y=212 width=45 height=54 xoffset=3 yoffset=14 xadvance=51 page=0 chnl=0
|
<char id="75" x="234" y="212" width="45" height="54" xoffset="3" yoffset="14" xadvance="46" page="0" chnl="0" />
|
||||||
char id=79 x=128 y=99 width=47 height=56 xoffset=1 yoffset=13 xadvance=50 page=0 chnl=0
|
<char id="76" x="279" y="212" width="36" height="54" xoffset="3" yoffset="14" xadvance="39" page="0" chnl="0" />
|
||||||
char id=80 x=418 y=212 width=43 height=54 xoffset=3 yoffset=14 xadvance=48 page=0 chnl=0
|
<char id="77" x="315" y="212" width="58" height="54" xoffset="3" yoffset="14" xadvance="63" page="0" chnl="0" />
|
||||||
char id=81 x=319 y=0 width=47 height=65 xoffset=2 yoffset=13 xadvance=50 page=0 chnl=0
|
<char id="78" x="373" y="212" width="45" height="54" xoffset="3" yoffset="14" xadvance="51" page="0" chnl="0" />
|
||||||
char id=82 x=461 y=212 width=43 height=54 xoffset=3 yoffset=14 xadvance=46 page=0 chnl=0
|
<char id="79" x="128" y="99" width="47" height="56" xoffset="1" yoffset="13" xadvance="50" page="0" chnl="0" />
|
||||||
char id=83 x=175 y=99 width=42 height=56 xoffset=1 yoffset=13 xadvance=44 page=0 chnl=0
|
<char id="80" x="418" y="212" width="43" height="54" xoffset="3" yoffset="14" xadvance="48" page="0" chnl="0" />
|
||||||
char id=84 x=0 y=266 width=45 height=54 xoffset=0 yoffset=14 xadvance=45 page=0 chnl=0
|
<char id="81" x="319" y="0" width="47" height="65" xoffset="2" yoffset="13" xadvance="50" page="0" chnl="0" />
|
||||||
char id=85 x=301 y=156 width=42 height=55 xoffset=3 yoffset=14 xadvance=48 page=0 chnl=0
|
<char id="82" x="461" y="212" width="43" height="54" xoffset="3" yoffset="14" xadvance="46" page="0" chnl="0" />
|
||||||
char id=86 x=45 y=266 width=51 height=54 xoffset=-2 yoffset=14 xadvance=48 page=0 chnl=0
|
<char id="83" x="175" y="99" width="42" height="56" xoffset="1" yoffset="13" xadvance="44" page="0" chnl="0" />
|
||||||
char id=87 x=96 y=266 width=64 height=54 xoffset=-1 yoffset=14 xadvance=63 page=0 chnl=0
|
<char id="84" x="0" y="266" width="45" height="54" xoffset="0" yoffset="14" xadvance="45" page="0" chnl="0" />
|
||||||
char id=88 x=160 y=266 width=48 height=54 xoffset=-1 yoffset=14 xadvance=46 page=0 chnl=0
|
<char id="85" x="301" y="156" width="42" height="55" xoffset="3" yoffset="14" xadvance="48" page="0" chnl="0" />
|
||||||
char id=89 x=208 y=266 width=49 height=54 xoffset=-2 yoffset=14 xadvance=45 page=0 chnl=0
|
<char id="86" x="45" y="266" width="51" height="54" xoffset="-2" yoffset="14" xadvance="48" page="0" chnl="0" />
|
||||||
char id=90 x=257 y=266 width=42 height=54 xoffset=1 yoffset=14 xadvance=44 page=0 chnl=0
|
<char id="87" x="96" y="266" width="64" height="54" xoffset="-1" yoffset="14" xadvance="63" page="0" chnl="0" />
|
||||||
char id=91 x=115 y=0 width=18 height=75 xoffset=3 yoffset=5 xadvance=21 page=0 chnl=0
|
<char id="88" x="160" y="266" width="48" height="54" xoffset="-1" yoffset="14" xadvance="46" page="0" chnl="0" />
|
||||||
char id=92 x=403 y=0 width=37 height=58 xoffset=-2 yoffset=14 xadvance=31 page=0 chnl=0
|
<char id="89" x="208" y="266" width="49" height="54" xoffset="-2" yoffset="14" xadvance="45" page="0" chnl="0" />
|
||||||
char id=93 x=133 y=0 width=18 height=75 xoffset=0 yoffset=5 xadvance=21 page=0 chnl=0
|
<char id="90" x="257" y="266" width="42" height="54" xoffset="1" yoffset="14" xadvance="44" page="0" chnl="0" />
|
||||||
char id=94 x=139 y=362 width=33 height=28 xoffset=0 yoffset=14 xadvance=32 page=0 chnl=0
|
<char id="91" x="115" y="0" width="18" height="75" xoffset="3" yoffset="5" xadvance="21" page="0" chnl="0" />
|
||||||
char id=95 x=331 y=362 width=34 height=12 xoffset=-1 yoffset=65 xadvance=33 page=0 chnl=0
|
<char id="92" x="403" y="0" width="37" height="58" xoffset="-2" yoffset="14" xadvance="31" page="0" chnl="0" />
|
||||||
char id=96 x=285 y=362 width=23 height=13 xoffset=0 yoffset=12 xadvance=24 page=0 chnl=0
|
<char id="93" x="133" y="0" width="18" height="75" xoffset="0" yoffset="5" xadvance="21" page="0" chnl="0" />
|
||||||
char id=97 x=0 y=320 width=37 height=42 xoffset=1 yoffset=27 xadvance=38 page=0 chnl=0
|
<char id="94" x="139" y="362" width="33" height="28" xoffset="0" yoffset="14" xadvance="32" page="0" chnl="0" />
|
||||||
char id=98 x=440 y=0 width=37 height=57 xoffset=2 yoffset=12 xadvance=40 page=0 chnl=0
|
<char id="95" x="331" y="362" width="34" height="12" xoffset="-1" yoffset="65" xadvance="33" page="0" chnl="0" />
|
||||||
char id=99 x=37 y=320 width=36 height=42 xoffset=1 yoffset=27 xadvance=38 page=0 chnl=0
|
<char id="96" x="285" y="362" width="23" height="13" xoffset="0" yoffset="12" xadvance="24" page="0" chnl="0" />
|
||||||
char id=100 x=0 y=99 width=37 height=57 xoffset=1 yoffset=12 xadvance=40 page=0 chnl=0
|
<char id="97" x="0" y="320" width="37" height="42" xoffset="1" yoffset="27" xadvance="38" page="0" chnl="0" />
|
||||||
char id=101 x=73 y=320 width=38 height=42 xoffset=1 yoffset=27 xadvance=39 page=0 chnl=0
|
<char id="98" x="440" y="0" width="37" height="57" xoffset="2" yoffset="12" xadvance="40" page="0" chnl="0" />
|
||||||
char id=102 x=477 y=0 width=28 height=57 xoffset=0 yoffset=11 xadvance=27 page=0 chnl=0
|
<char id="99" x="37" y="320" width="36" height="42" xoffset="1" yoffset="27" xadvance="38" page="0" chnl="0" />
|
||||||
char id=103 x=217 y=99 width=38 height=56 xoffset=1 yoffset=27 xadvance=41 page=0 chnl=0
|
<char id="100" x="0" y="99" width="37" height="57" xoffset="1" yoffset="12" xadvance="40" page="0" chnl="0" />
|
||||||
char id=104 x=255 y=99 width=36 height=56 xoffset=2 yoffset=12 xadvance=40 page=0 chnl=0
|
<char id="101" x="73" y="320" width="38" height="42" xoffset="1" yoffset="27" xadvance="39" page="0" chnl="0" />
|
||||||
char id=105 x=291 y=99 width=15 height=56 xoffset=2 yoffset=12 xadvance=19 page=0 chnl=0
|
<char id="102" x="477" y="0" width="28" height="57" xoffset="0" yoffset="11" xadvance="27" page="0" chnl="0" />
|
||||||
char id=106 x=197 y=0 width=23 height=71 xoffset=-5 yoffset=12 xadvance=20 page=0 chnl=0
|
<char id="103" x="217" y="99" width="38" height="56" xoffset="1" yoffset="27" xadvance="41" page="0" chnl="0" />
|
||||||
char id=107 x=306 y=99 width=40 height=56 xoffset=2 yoffset=12 xadvance=39 page=0 chnl=0
|
<char id="104" x="255" y="99" width="36" height="56" xoffset="2" yoffset="12" xadvance="40" page="0" chnl="0" />
|
||||||
char id=108 x=346 y=99 width=14 height=56 xoffset=3 yoffset=12 xadvance=20 page=0 chnl=0
|
<char id="105" x="291" y="99" width="15" height="56" xoffset="2" yoffset="12" xadvance="19" page="0" chnl="0" />
|
||||||
char id=109 x=186 y=320 width=58 height=41 xoffset=2 yoffset=27 xadvance=63 page=0 chnl=0
|
<char id="106" x="197" y="0" width="23" height="71" xoffset="-5" yoffset="12" xadvance="20" page="0" chnl="0" />
|
||||||
char id=110 x=244 y=320 width=36 height=41 xoffset=2 yoffset=27 xadvance=40 page=0 chnl=0
|
<char id="107" x="306" y="99" width="40" height="56" xoffset="2" yoffset="12" xadvance="39" page="0" chnl="0" />
|
||||||
char id=111 x=111 y=320 width=39 height=42 xoffset=1 yoffset=27 xadvance=41 page=0 chnl=0
|
<char id="108" x="346" y="99" width="14" height="56" xoffset="3" yoffset="12" xadvance="20" page="0" chnl="0" />
|
||||||
char id=112 x=360 y=99 width=37 height=56 xoffset=2 yoffset=27 xadvance=40 page=0 chnl=0
|
<char id="109" x="186" y="320" width="58" height="41" xoffset="2" yoffset="27" xadvance="63" page="0" chnl="0" />
|
||||||
char id=113 x=397 y=99 width=37 height=56 xoffset=1 yoffset=27 xadvance=40 page=0 chnl=0
|
<char id="110" x="244" y="320" width="36" height="41" xoffset="2" yoffset="27" xadvance="40" page="0" chnl="0" />
|
||||||
char id=114 x=486 y=266 width=25 height=41 xoffset=2 yoffset=27 xadvance=27 page=0 chnl=0
|
<char id="111" x="111" y="320" width="39" height="42" xoffset="1" yoffset="27" xadvance="41" page="0" chnl="0" />
|
||||||
char id=115 x=150 y=320 width=36 height=42 xoffset=0 yoffset=27 xadvance=37 page=0 chnl=0
|
<char id="112" x="360" y="99" width="37" height="56" xoffset="2" yoffset="27" xadvance="40" page="0" chnl="0" />
|
||||||
char id=116 x=445 y=266 width=26 height=51 xoffset=0 yoffset=18 xadvance=25 page=0 chnl=0
|
<char id="113" x="397" y="99" width="37" height="56" xoffset="1" yoffset="27" xadvance="40" page="0" chnl="0" />
|
||||||
char id=117 x=280 y=320 width=36 height=41 xoffset=2 yoffset=28 xadvance=40 page=0 chnl=0
|
<char id="114" x="486" y="266" width="25" height="41" xoffset="2" yoffset="27" xadvance="27" page="0" chnl="0" />
|
||||||
char id=118 x=316 y=320 width=39 height=40 xoffset=-1 yoffset=28 xadvance=37 page=0 chnl=0
|
<char id="115" x="150" y="320" width="36" height="42" xoffset="0" yoffset="27" xadvance="37" page="0" chnl="0" />
|
||||||
char id=119 x=355 y=320 width=54 height=40 xoffset=-1 yoffset=28 xadvance=52 page=0 chnl=0
|
<char id="116" x="445" y="266" width="26" height="51" xoffset="0" yoffset="18" xadvance="25" page="0" chnl="0" />
|
||||||
char id=120 x=409 y=320 width=40 height=40 xoffset=-1 yoffset=28 xadvance=37 page=0 chnl=0
|
<char id="117" x="280" y="320" width="36" height="41" xoffset="2" yoffset="28" xadvance="40" page="0" chnl="0" />
|
||||||
char id=121 x=343 y=156 width=40 height=55 xoffset=-1 yoffset=28 xadvance=37 page=0 chnl=0
|
<char id="118" x="316" y="320" width="39" height="40" xoffset="-1" yoffset="28" xadvance="37" page="0" chnl="0" />
|
||||||
char id=122 x=449 y=320 width=34 height=40 xoffset=1 yoffset=28 xadvance=36 page=0 chnl=0
|
<char id="119" x="355" y="320" width="54" height="40" xoffset="-1" yoffset="28" xadvance="52" page="0" chnl="0" />
|
||||||
char id=123 x=151 y=0 width=23 height=72 xoffset=0 yoffset=9 xadvance=23 page=0 chnl=0
|
<char id="120" x="409" y="320" width="40" height="40" xoffset="-1" yoffset="28" xadvance="37" page="0" chnl="0" />
|
||||||
char id=124 x=366 y=0 width=8 height=63 xoffset=5 yoffset=14 xadvance=18 page=0 chnl=0
|
<char id="121" x="343" y="156" width="40" height="55" xoffset="-1" yoffset="28" xadvance="37" page="0" chnl="0" />
|
||||||
char id=125 x=174 y=0 width=23 height=72 xoffset=0 yoffset=9 xadvance=23 page=0 chnl=0
|
<char id="122" x="449" y="320" width="34" height="40" xoffset="1" yoffset="28" xadvance="36" page="0" chnl="0" />
|
||||||
char id=126 x=229 y=362 width=41 height=19 xoffset=2 yoffset=36 xadvance=45 page=0 chnl=0
|
<char id="123" x="151" y="0" width="23" height="72" xoffset="0" yoffset="9" xadvance="23" page="0" chnl="0" />
|
||||||
char id=127 x=0 y=0 width=70 height=99 xoffset=2 yoffset=-11 xadvance=74 page=0 chnl=0
|
<char id="124" x="366" y="0" width="8" height="63" xoffset="5" yoffset="14" xadvance="18" page="0" chnl="0" />
|
||||||
kernings count=385
|
<char id="125" x="174" y="0" width="23" height="72" xoffset="0" yoffset="9" xadvance="23" page="0" chnl="0" />
|
||||||
kerning first=84 second=74 amount=-8
|
<char id="126" x="229" y="362" width="41" height="19" xoffset="2" yoffset="36" xadvance="45" page="0" chnl="0" />
|
||||||
kerning first=86 second=100 amount=-2
|
<char id="127" x="0" y="0" width="70" height="99" xoffset="2" yoffset="-11" xadvance="74" page="0" chnl="0" />
|
||||||
kerning first=114 second=113 amount=-1
|
</chars>
|
||||||
kerning first=70 second=121 amount=-1
|
<kernings count="385">
|
||||||
kerning first=34 second=99 amount=-2
|
<kerning first="84" second="74" amount="-8" />
|
||||||
kerning first=70 second=99 amount=-1
|
<kerning first="86" second="100" amount="-2" />
|
||||||
kerning first=69 second=99 amount=-1
|
<kerning first="114" second="113" amount="-1" />
|
||||||
kerning first=88 second=113 amount=-1
|
<kerning first="70" second="121" amount="-1" />
|
||||||
kerning first=84 second=46 amount=-9
|
<kerning first="34" second="99" amount="-2" />
|
||||||
kerning first=87 second=97 amount=-1
|
<kerning first="70" second="99" amount="-1" />
|
||||||
kerning first=90 second=117 amount=-1
|
<kerning first="69" second="99" amount="-1" />
|
||||||
kerning first=39 second=97 amount=-2
|
<kerning first="88" second="113" amount="-1" />
|
||||||
kerning first=69 second=111 amount=-1
|
<kerning first="84" second="46" amount="-9" />
|
||||||
kerning first=87 second=41 amount=1
|
<kerning first="87" second="97" amount="-1" />
|
||||||
kerning first=121 second=34 amount=1
|
<kerning first="90" second="117" amount="-1" />
|
||||||
kerning first=40 second=86 amount=1
|
<kerning first="39" second="97" amount="-2" />
|
||||||
kerning first=85 second=65 amount=-1
|
<kerning first="69" second="111" amount="-1" />
|
||||||
kerning first=72 second=65 amount=1
|
<kerning first="87" second="41" amount="1" />
|
||||||
kerning first=114 second=102 amount=1
|
<kerning first="121" second="34" amount="1" />
|
||||||
kerning first=89 second=42 amount=-2
|
<kerning first="40" second="86" amount="1" />
|
||||||
kerning first=114 second=34 amount=1
|
<kerning first="85" second="65" amount="-1" />
|
||||||
kerning first=75 second=67 amount=-1
|
<kerning first="72" second="65" amount="1" />
|
||||||
kerning first=89 second=85 amount=-3
|
<kerning first="114" second="102" amount="1" />
|
||||||
kerning first=77 second=88 amount=1
|
<kerning first="89" second="42" amount="-2" />
|
||||||
kerning first=84 second=115 amount=-3
|
<kerning first="114" second="34" amount="1" />
|
||||||
kerning first=84 second=71 amount=-1
|
<kerning first="75" second="67" amount="-1" />
|
||||||
kerning first=89 second=101 amount=-2
|
<kerning first="89" second="85" amount="-3" />
|
||||||
kerning first=89 second=45 amount=-5
|
<kerning first="77" second="88" amount="1" />
|
||||||
kerning first=78 second=88 amount=1
|
<kerning first="84" second="115" amount="-3" />
|
||||||
kerning first=68 second=89 amount=-2
|
<kerning first="84" second="71" amount="-1" />
|
||||||
kerning first=122 second=103 amount=-1
|
<kerning first="89" second="101" amount="-2" />
|
||||||
kerning first=78 second=84 amount=-1
|
<kerning first="89" second="45" amount="-5" />
|
||||||
kerning first=86 second=103 amount=-2
|
<kerning first="78" second="88" amount="1" />
|
||||||
kerning first=89 second=79 amount=-1
|
<kerning first="68" second="89" amount="-2" />
|
||||||
kerning first=75 second=111 amount=-1
|
<kerning first="122" second="103" amount="-1" />
|
||||||
kerning first=111 second=120 amount=-1
|
<kerning first="78" second="84" amount="-1" />
|
||||||
kerning first=87 second=44 amount=-5
|
<kerning first="86" second="103" amount="-2" />
|
||||||
kerning first=67 second=84 amount=-1
|
<kerning first="89" second="79" amount="-1" />
|
||||||
kerning first=84 second=111 amount=-7
|
<kerning first="75" second="111" amount="-1" />
|
||||||
kerning first=84 second=83 amount=-1
|
<kerning first="111" second="120" amount="-1" />
|
||||||
kerning first=102 second=113 amount=-1
|
<kerning first="87" second="44" amount="-5" />
|
||||||
kerning first=39 second=101 amount=-2
|
<kerning first="67" second="84" amount="-1" />
|
||||||
kerning first=80 second=88 amount=-2
|
<kerning first="84" second="111" amount="-7" />
|
||||||
kerning first=66 second=84 amount=-1
|
<kerning first="84" second="83" amount="-1" />
|
||||||
kerning first=65 second=87 amount=-1
|
<kerning first="102" second="113" amount="-1" />
|
||||||
kerning first=122 second=100 amount=-1
|
<kerning first="39" second="101" amount="-2" />
|
||||||
kerning first=75 second=118 amount=-1
|
<kerning first="80" second="88" amount="-2" />
|
||||||
kerning first=73 second=65 amount=1
|
<kerning first="66" second="84" amount="-1" />
|
||||||
kerning first=70 second=118 amount=-1
|
<kerning first="65" second="87" amount="-1" />
|
||||||
kerning first=73 second=88 amount=1
|
<kerning first="122" second="100" amount="-1" />
|
||||||
kerning first=82 second=89 amount=-2
|
<kerning first="75" second="118" amount="-1" />
|
||||||
kerning first=65 second=34 amount=-4
|
<kerning first="73" second="65" amount="1" />
|
||||||
kerning first=120 second=99 amount=-1
|
<kerning first="70" second="118" amount="-1" />
|
||||||
kerning first=84 second=99 amount=-3
|
<kerning first="73" second="88" amount="1" />
|
||||||
kerning first=84 second=65 amount=-4
|
<kerning first="82" second="89" amount="-2" />
|
||||||
kerning first=112 second=39 amount=-1
|
<kerning first="65" second="34" amount="-4" />
|
||||||
kerning first=76 second=39 amount=-10
|
<kerning first="120" second="99" amount="-1" />
|
||||||
kerning first=78 second=65 amount=1
|
<kerning first="84" second="99" amount="-3" />
|
||||||
kerning first=88 second=45 amount=-5
|
<kerning first="84" second="65" amount="-4" />
|
||||||
kerning first=34 second=111 amount=-3
|
<kerning first="112" second="39" amount="-1" />
|
||||||
kerning first=114 second=99 amount=-1
|
<kerning first="76" second="39" amount="-10" />
|
||||||
kerning first=86 second=125 amount=1
|
<kerning first="78" second="65" amount="1" />
|
||||||
kerning first=70 second=111 amount=-1
|
<kerning first="88" second="45" amount="-5" />
|
||||||
kerning first=89 second=120 amount=-1
|
<kerning first="34" second="111" amount="-3" />
|
||||||
kerning first=90 second=119 amount=-1
|
<kerning first="114" second="99" amount="-1" />
|
||||||
kerning first=89 second=89 amount=1
|
<kerning first="86" second="125" amount="1" />
|
||||||
kerning first=89 second=117 amount=-1
|
<kerning first="70" second="111" amount="-1" />
|
||||||
kerning first=75 second=117 amount=-1
|
<kerning first="89" second="120" amount="-1" />
|
||||||
kerning first=76 second=65 amount=1
|
<kerning first="90" second="119" amount="-1" />
|
||||||
kerning first=34 second=34 amount=-1
|
<kerning first="89" second="89" amount="1" />
|
||||||
kerning first=89 second=110 amount=-1
|
<kerning first="89" second="117" amount="-1" />
|
||||||
kerning first=88 second=101 amount=-1
|
<kerning first="75" second="117" amount="-1" />
|
||||||
kerning first=107 second=103 amount=-1
|
<kerning first="76" second="65" amount="1" />
|
||||||
kerning first=34 second=115 amount=-3
|
<kerning first="34" second="34" amount="-1" />
|
||||||
kerning first=80 second=44 amount=-14
|
<kerning first="89" second="110" amount="-1" />
|
||||||
kerning first=98 second=39 amount=-1
|
<kerning first="88" second="101" amount="-1" />
|
||||||
kerning first=70 second=65 amount=-7
|
<kerning first="107" second="103" amount="-1" />
|
||||||
kerning first=89 second=116 amount=-1
|
<kerning first="34" second="115" amount="-3" />
|
||||||
kerning first=70 second=46 amount=-10
|
<kerning first="80" second="44" amount="-14" />
|
||||||
kerning first=98 second=34 amount=-1
|
<kerning first="98" second="39" amount="-1" />
|
||||||
kerning first=70 second=84 amount=1
|
<kerning first="70" second="65" amount="-7" />
|
||||||
kerning first=114 second=100 amount=-1
|
<kerning first="89" second="116" amount="-1" />
|
||||||
kerning first=88 second=79 amount=-1
|
<kerning first="70" second="46" amount="-10" />
|
||||||
kerning first=39 second=113 amount=-2
|
<kerning first="98" second="34" amount="-1" />
|
||||||
kerning first=65 second=118 amount=-2
|
<kerning first="70" second="84" amount="1" />
|
||||||
kerning first=114 second=103 amount=-1
|
<kerning first="114" second="100" amount="-1" />
|
||||||
kerning first=77 second=65 amount=1
|
<kerning first="88" second="79" amount="-1" />
|
||||||
kerning first=120 second=103 amount=-1
|
<kerning first="39" second="113" amount="-2" />
|
||||||
kerning first=65 second=110 amount=-2
|
<kerning first="65" second="118" amount="-2" />
|
||||||
kerning first=114 second=121 amount=1
|
<kerning first="114" second="103" amount="-1" />
|
||||||
kerning first=89 second=100 amount=-2
|
<kerning first="77" second="65" amount="1" />
|
||||||
kerning first=80 second=65 amount=-6
|
<kerning first="120" second="103" amount="-1" />
|
||||||
kerning first=121 second=111 amount=-1
|
<kerning first="65" second="110" amount="-2" />
|
||||||
kerning first=34 second=101 amount=-2
|
<kerning first="114" second="121" amount="1" />
|
||||||
kerning first=122 second=111 amount=-1
|
<kerning first="89" second="100" amount="-2" />
|
||||||
kerning first=114 second=118 amount=1
|
<kerning first="80" second="65" amount="-6" />
|
||||||
kerning first=102 second=41 amount=1
|
<kerning first="121" second="111" amount="-1" />
|
||||||
kerning first=122 second=113 amount=-1
|
<kerning first="34" second="101" amount="-2" />
|
||||||
kerning first=89 second=122 amount=-1
|
<kerning first="122" second="111" amount="-1" />
|
||||||
kerning first=68 second=88 amount=-1
|
<kerning first="114" second="118" amount="1" />
|
||||||
kerning first=81 second=89 amount=-1
|
<kerning first="102" second="41" amount="1" />
|
||||||
kerning first=114 second=111 amount=-1
|
<kerning first="122" second="113" amount="-1" />
|
||||||
kerning first=46 second=34 amount=-10
|
<kerning first="89" second="122" amount="-1" />
|
||||||
kerning first=84 second=112 amount=-3
|
<kerning first="68" second="88" amount="-1" />
|
||||||
kerning first=76 second=34 amount=-10
|
<kerning first="81" second="89" amount="-1" />
|
||||||
kerning first=39 second=115 amount=-3
|
<kerning first="114" second="111" amount="-1" />
|
||||||
kerning first=76 second=118 amount=-4
|
<kerning first="46" second="34" amount="-10" />
|
||||||
kerning first=86 second=99 amount=-2
|
<kerning first="84" second="112" amount="-3" />
|
||||||
kerning first=84 second=84 amount=1
|
<kerning first="76" second="34" amount="-10" />
|
||||||
kerning first=120 second=111 amount=-1
|
<kerning first="39" second="115" amount="-3" />
|
||||||
kerning first=65 second=79 amount=-1
|
<kerning first="76" second="118" amount="-4" />
|
||||||
kerning first=87 second=101 amount=-1
|
<kerning first="86" second="99" amount="-2" />
|
||||||
kerning first=67 second=125 amount=-1
|
<kerning first="84" second="84" amount="1" />
|
||||||
kerning first=120 second=113 amount=-1
|
<kerning first="120" second="111" amount="-1" />
|
||||||
kerning first=118 second=46 amount=-6
|
<kerning first="65" second="79" amount="-1" />
|
||||||
kerning first=88 second=103 amount=-1
|
<kerning first="87" second="101" amount="-1" />
|
||||||
kerning first=111 second=122 amount=-1
|
<kerning first="67" second="125" amount="-1" />
|
||||||
kerning first=77 second=84 amount=-1
|
<kerning first="120" second="113" amount="-1" />
|
||||||
kerning first=114 second=46 amount=-6
|
<kerning first="118" second="46" amount="-6" />
|
||||||
kerning first=34 second=39 amount=-1
|
<kerning first="88" second="103" amount="-1" />
|
||||||
kerning first=65 second=121 amount=-2
|
<kerning first="111" second="122" amount="-1" />
|
||||||
kerning first=114 second=44 amount=-6
|
<kerning first="77" second="84" amount="-1" />
|
||||||
kerning first=69 second=84 amount=1
|
<kerning first="114" second="46" amount="-6" />
|
||||||
kerning first=89 second=46 amount=-8
|
<kerning first="34" second="39" amount="-1" />
|
||||||
kerning first=97 second=39 amount=-1
|
<kerning first="65" second="121" amount="-2" />
|
||||||
kerning first=34 second=100 amount=-2
|
<kerning first="114" second="44" amount="-6" />
|
||||||
kerning first=70 second=100 amount=-1
|
<kerning first="69" second="84" amount="1" />
|
||||||
kerning first=84 second=120 amount=-3
|
<kerning first="89" second="46" amount="-8" />
|
||||||
kerning first=90 second=118 amount=-1
|
<kerning first="97" second="39" amount="-1" />
|
||||||
kerning first=70 second=114 amount=-1
|
<kerning first="34" second="100" amount="-2" />
|
||||||
kerning first=34 second=112 amount=-1
|
<kerning first="70" second="100" amount="-1" />
|
||||||
kerning first=89 second=86 amount=1
|
<kerning first="84" second="120" amount="-3" />
|
||||||
kerning first=86 second=113 amount=-2
|
<kerning first="90" second="118" amount="-1" />
|
||||||
kerning first=88 second=71 amount=-1
|
<kerning first="70" second="114" amount="-1" />
|
||||||
kerning first=122 second=99 amount=-1
|
<kerning first="34" second="112" amount="-1" />
|
||||||
kerning first=66 second=89 amount=-2
|
<kerning first="89" second="86" amount="1" />
|
||||||
kerning first=102 second=103 amount=-1
|
<kerning first="86" second="113" amount="-2" />
|
||||||
kerning first=88 second=67 amount=-1
|
<kerning first="88" second="71" amount="-1" />
|
||||||
kerning first=39 second=110 amount=-1
|
<kerning first="122" second="99" amount="-1" />
|
||||||
kerning first=88 second=117 amount=-1
|
<kerning first="66" second="89" amount="-2" />
|
||||||
kerning first=89 second=118 amount=-1
|
<kerning first="102" second="103" amount="-1" />
|
||||||
kerning first=97 second=118 amount=-1
|
<kerning first="88" second="67" amount="-1" />
|
||||||
kerning first=87 second=65 amount=-2
|
<kerning first="39" second="110" amount="-1" />
|
||||||
kerning first=89 second=67 amount=-1
|
<kerning first="88" second="117" amount="-1" />
|
||||||
kerning first=89 second=74 amount=-3
|
<kerning first="89" second="118" amount="-1" />
|
||||||
kerning first=102 second=101 amount=-1
|
<kerning first="97" second="118" amount="-1" />
|
||||||
kerning first=86 second=111 amount=-2
|
<kerning first="87" second="65" amount="-2" />
|
||||||
kerning first=65 second=119 amount=-1
|
<kerning first="89" second="67" amount="-1" />
|
||||||
kerning first=84 second=100 amount=-3
|
<kerning first="89" second="74" amount="-3" />
|
||||||
kerning first=120 second=100 amount=-1
|
<kerning first="102" second="101" amount="-1" />
|
||||||
kerning first=104 second=34 amount=-3
|
<kerning first="86" second="111" amount="-2" />
|
||||||
kerning first=86 second=41 amount=1
|
<kerning first="65" second="119" amount="-1" />
|
||||||
kerning first=111 second=34 amount=-3
|
<kerning first="84" second="100" amount="-3" />
|
||||||
kerning first=40 second=89 amount=1
|
<kerning first="120" second="100" amount="-1" />
|
||||||
kerning first=121 second=39 amount=1
|
<kerning first="104" second="34" amount="-3" />
|
||||||
kerning first=70 second=74 amount=-7
|
<kerning first="86" second="41" amount="1" />
|
||||||
kerning first=68 second=90 amount=-1
|
<kerning first="111" second="34" amount="-3" />
|
||||||
kerning first=98 second=120 amount=-1
|
<kerning first="40" second="89" amount="1" />
|
||||||
kerning first=110 second=34 amount=-3
|
<kerning first="121" second="39" amount="1" />
|
||||||
kerning first=119 second=46 amount=-4
|
<kerning first="70" second="74" amount="-7" />
|
||||||
kerning first=69 second=102 amount=-1
|
<kerning first="68" second="90" amount="-1" />
|
||||||
kerning first=118 second=44 amount=-6
|
<kerning first="98" second="120" amount="-1" />
|
||||||
kerning first=84 second=114 amount=-2
|
<kerning first="110" second="34" amount="-3" />
|
||||||
kerning first=86 second=97 amount=-2
|
<kerning first="119" second="46" amount="-4" />
|
||||||
kerning first=40 second=87 amount=1
|
<kerning first="69" second="102" amount="-1" />
|
||||||
kerning first=65 second=109 amount=-2
|
<kerning first="118" second="44" amount="-6" />
|
||||||
kerning first=68 second=86 amount=-1
|
<kerning first="84" second="114" amount="-2" />
|
||||||
kerning first=86 second=93 amount=1
|
<kerning first="86" second="97" amount="-2" />
|
||||||
kerning first=65 second=67 amount=-1
|
<kerning first="40" second="87" amount="1" />
|
||||||
kerning first=97 second=34 amount=-1
|
<kerning first="65" second="109" amount="-2" />
|
||||||
kerning first=34 second=65 amount=-4
|
<kerning first="68" second="86" amount="-1" />
|
||||||
kerning first=84 second=118 amount=-3
|
<kerning first="86" second="93" amount="1" />
|
||||||
kerning first=112 second=34 amount=-1
|
<kerning first="65" second="67" amount="-1" />
|
||||||
kerning first=76 second=84 amount=-7
|
<kerning first="97" second="34" amount="-1" />
|
||||||
kerning first=107 second=99 amount=-1
|
<kerning first="34" second="65" amount="-4" />
|
||||||
kerning first=123 second=85 amount=-1
|
<kerning first="84" second="118" amount="-3" />
|
||||||
kerning first=102 second=125 amount=1
|
<kerning first="112" second="34" amount="-1" />
|
||||||
kerning first=65 second=63 amount=-3
|
<kerning first="76" second="84" amount="-7" />
|
||||||
kerning first=89 second=44 amount=-8
|
<kerning first="107" second="99" amount="-1" />
|
||||||
kerning first=80 second=118 amount=1
|
<kerning first="123" second="85" amount="-1" />
|
||||||
kerning first=112 second=122 amount=-1
|
<kerning first="102" second="125" amount="1" />
|
||||||
kerning first=79 second=65 amount=-1
|
<kerning first="65" second="63" amount="-3" />
|
||||||
kerning first=80 second=121 amount=1
|
<kerning first="89" second="44" amount="-8" />
|
||||||
kerning first=118 second=34 amount=1
|
<kerning first="80" second="118" amount="1" />
|
||||||
kerning first=87 second=45 amount=-2
|
<kerning first="112" second="122" amount="-1" />
|
||||||
kerning first=69 second=100 amount=-1
|
<kerning first="79" second="65" amount="-1" />
|
||||||
kerning first=87 second=103 amount=-1
|
<kerning first="80" second="121" amount="1" />
|
||||||
kerning first=112 second=120 amount=-1
|
<kerning first="118" second="34" amount="1" />
|
||||||
kerning first=86 second=65 amount=-3
|
<kerning first="87" second="45" amount="-2" />
|
||||||
kerning first=65 second=81 amount=-1
|
<kerning first="69" second="100" amount="-1" />
|
||||||
kerning first=68 second=44 amount=-4
|
<kerning first="87" second="103" amount="-1" />
|
||||||
kerning first=86 second=45 amount=-6
|
<kerning first="112" second="120" amount="-1" />
|
||||||
kerning first=39 second=34 amount=-1
|
<kerning first="86" second="65" amount="-3" />
|
||||||
kerning first=72 second=88 amount=1
|
<kerning first="65" second="81" amount="-1" />
|
||||||
kerning first=68 second=46 amount=-4
|
<kerning first="68" second="44" amount="-4" />
|
||||||
kerning first=65 second=89 amount=-5
|
<kerning first="86" second="45" amount="-6" />
|
||||||
kerning first=69 second=118 amount=-1
|
<kerning first="39" second="34" amount="-1" />
|
||||||
kerning first=89 second=38 amount=-1
|
<kerning first="72" second="88" amount="1" />
|
||||||
kerning first=88 second=99 amount=-1
|
<kerning first="68" second="46" amount="-4" />
|
||||||
kerning first=65 second=71 amount=-1
|
<kerning first="65" second="89" amount="-5" />
|
||||||
kerning first=91 second=74 amount=-1
|
<kerning first="69" second="118" amount="-1" />
|
||||||
kerning first=75 second=101 amount=-1
|
<kerning first="89" second="38" amount="-1" />
|
||||||
kerning first=39 second=112 amount=-1
|
<kerning first="88" second="99" amount="-1" />
|
||||||
kerning first=70 second=113 amount=-1
|
<kerning first="65" second="71" amount="-1" />
|
||||||
kerning first=119 second=44 amount=-4
|
<kerning first="91" second="74" amount="-1" />
|
||||||
kerning first=72 second=89 amount=-1
|
<kerning first="75" second="101" amount="-1" />
|
||||||
kerning first=90 second=103 amount=-1
|
<kerning first="39" second="112" amount="-1" />
|
||||||
kerning first=65 second=86 amount=-3
|
<kerning first="70" second="113" amount="-1" />
|
||||||
kerning first=84 second=119 amount=-2
|
<kerning first="119" second="44" amount="-4" />
|
||||||
kerning first=34 second=110 amount=-1
|
<kerning first="72" second="89" amount="-1" />
|
||||||
kerning first=39 second=109 amount=-1
|
<kerning first="90" second="103" amount="-1" />
|
||||||
kerning first=75 second=81 amount=-1
|
<kerning first="65" second="86" amount="-3" />
|
||||||
kerning first=89 second=115 amount=-2
|
<kerning first="84" second="119" amount="-2" />
|
||||||
kerning first=89 second=87 amount=1
|
<kerning first="34" second="110" amount="-1" />
|
||||||
kerning first=114 second=101 amount=-1
|
<kerning first="39" second="109" amount="-1" />
|
||||||
kerning first=116 second=111 amount=-1
|
<kerning first="75" second="81" amount="-1" />
|
||||||
kerning first=90 second=100 amount=-1
|
<kerning first="89" second="115" amount="-2" />
|
||||||
kerning first=79 second=89 amount=-2
|
<kerning first="89" second="87" amount="1" />
|
||||||
kerning first=84 second=122 amount=-2
|
<kerning first="114" second="101" amount="-1" />
|
||||||
kerning first=68 second=84 amount=-3
|
<kerning first="116" second="111" amount="-1" />
|
||||||
kerning first=76 second=86 amount=-7
|
<kerning first="90" second="100" amount="-1" />
|
||||||
kerning first=74 second=65 amount=-1
|
<kerning first="79" second="89" amount="-2" />
|
||||||
kerning first=107 second=101 amount=-1
|
<kerning first="84" second="122" amount="-2" />
|
||||||
kerning first=80 second=46 amount=-14
|
<kerning first="68" second="84" amount="-3" />
|
||||||
kerning first=89 second=93 amount=1
|
<kerning first="76" second="86" amount="-7" />
|
||||||
kerning first=89 second=65 amount=-5
|
<kerning first="74" second="65" amount="-1" />
|
||||||
kerning first=87 second=117 amount=-1
|
<kerning first="107" second="101" amount="-1" />
|
||||||
kerning first=89 second=81 amount=-1
|
<kerning first="80" second="46" amount="-14" />
|
||||||
kerning first=39 second=103 amount=-2
|
<kerning first="89" second="93" amount="1" />
|
||||||
kerning first=86 second=101 amount=-2
|
<kerning first="89" second="65" amount="-5" />
|
||||||
kerning first=86 second=117 amount=-1
|
<kerning first="87" second="117" amount="-1" />
|
||||||
kerning first=84 second=113 amount=-3
|
<kerning first="89" second="81" amount="-1" />
|
||||||
kerning first=87 second=46 amount=-5
|
<kerning first="39" second="103" amount="-2" />
|
||||||
kerning first=47 second=47 amount=-9
|
<kerning first="86" second="101" amount="-2" />
|
||||||
kerning first=75 second=103 amount=-1
|
<kerning first="86" second="117" amount="-1" />
|
||||||
kerning first=89 second=84 amount=1
|
<kerning first="84" second="113" amount="-3" />
|
||||||
kerning first=84 second=110 amount=-3
|
<kerning first="87" second="46" amount="-5" />
|
||||||
kerning first=39 second=99 amount=-2
|
<kerning first="47" second="47" amount="-9" />
|
||||||
kerning first=88 second=121 amount=-1
|
<kerning first="75" second="103" amount="-1" />
|
||||||
kerning first=65 second=39 amount=-4
|
<kerning first="89" second="84" amount="1" />
|
||||||
kerning first=110 second=39 amount=-3
|
<kerning first="84" second="110" amount="-3" />
|
||||||
kerning first=88 second=118 amount=-1
|
<kerning first="39" second="99" amount="-2" />
|
||||||
kerning first=86 second=114 amount=-1
|
<kerning first="88" second="121" amount="-1" />
|
||||||
kerning first=80 second=74 amount=-6
|
<kerning first="65" second="39" amount="-4" />
|
||||||
kerning first=84 second=97 amount=-6
|
<kerning first="110" second="39" amount="-3" />
|
||||||
kerning first=82 second=84 amount=-2
|
<kerning first="88" second="118" amount="-1" />
|
||||||
kerning first=91 second=85 amount=-1
|
<kerning first="86" second="114" amount="-1" />
|
||||||
kerning first=102 second=99 amount=-1
|
<kerning first="80" second="74" amount="-6" />
|
||||||
kerning first=66 second=86 amount=-1
|
<kerning first="84" second="97" amount="-6" />
|
||||||
kerning first=120 second=101 amount=-1
|
<kerning first="82" second="84" amount="-2" />
|
||||||
kerning first=102 second=93 amount=1
|
<kerning first="91" second="85" amount="-1" />
|
||||||
kerning first=75 second=100 amount=-1
|
<kerning first="102" second="99" amount="-1" />
|
||||||
kerning first=84 second=79 amount=-1
|
<kerning first="66" second="86" amount="-1" />
|
||||||
kerning first=44 second=39 amount=-10
|
<kerning first="120" second="101" amount="-1" />
|
||||||
kerning first=111 second=121 amount=-1
|
<kerning first="102" second="93" amount="1" />
|
||||||
kerning first=75 second=121 amount=-1
|
<kerning first="75" second="100" amount="-1" />
|
||||||
kerning first=81 second=87 amount=-1
|
<kerning first="84" second="79" amount="-1" />
|
||||||
kerning first=107 second=113 amount=-1
|
<kerning first="44" second="39" amount="-10" />
|
||||||
kerning first=90 second=79 amount=-1
|
<kerning first="111" second="121" amount="-1" />
|
||||||
kerning first=89 second=114 amount=-1
|
<kerning first="75" second="121" amount="-1" />
|
||||||
kerning first=122 second=101 amount=-1
|
<kerning first="81" second="87" amount="-1" />
|
||||||
kerning first=111 second=118 amount=-1
|
<kerning first="107" second="113" amount="-1" />
|
||||||
kerning first=82 second=86 amount=-1
|
<kerning first="90" second="79" amount="-1" />
|
||||||
kerning first=70 second=101 amount=-1
|
<kerning first="89" second="114" amount="-1" />
|
||||||
kerning first=114 second=97 amount=-1
|
<kerning first="122" second="101" amount="-1" />
|
||||||
kerning first=70 second=97 amount=-1
|
<kerning first="111" second="118" amount="-1" />
|
||||||
kerning first=34 second=97 amount=-2
|
<kerning first="82" second="86" amount="-1" />
|
||||||
kerning first=89 second=102 amount=-1
|
<kerning first="70" second="101" amount="-1" />
|
||||||
kerning first=78 second=89 amount=-1
|
<kerning first="114" second="97" amount="-1" />
|
||||||
kerning first=70 second=44 amount=-10
|
<kerning first="70" second="97" amount="-1" />
|
||||||
kerning first=104 second=39 amount=-3
|
<kerning first="34" second="97" amount="-2" />
|
||||||
kerning first=84 second=45 amount=-10
|
<kerning first="89" second="102" amount="-1" />
|
||||||
kerning first=89 second=121 amount=-1
|
<kerning first="78" second="89" amount="-1" />
|
||||||
kerning first=109 second=34 amount=-3
|
<kerning first="70" second="44" amount="-10" />
|
||||||
kerning first=84 second=86 amount=1
|
<kerning first="104" second="39" amount="-3" />
|
||||||
kerning first=87 second=99 amount=-1
|
<kerning first="84" second="45" amount="-10" />
|
||||||
kerning first=32 second=84 amount=-2
|
<kerning first="89" second="121" amount="-1" />
|
||||||
kerning first=98 second=122 amount=-1
|
<kerning first="109" second="34" amount="-3" />
|
||||||
kerning first=89 second=112 amount=-1
|
<kerning first="84" second="86" amount="1" />
|
||||||
kerning first=89 second=103 amount=-2
|
<kerning first="87" second="99" amount="-1" />
|
||||||
kerning first=65 second=116 amount=-1
|
<kerning first="32" second="84" amount="-2" />
|
||||||
kerning first=88 second=81 amount=-1
|
<kerning first="98" second="122" amount="-1" />
|
||||||
kerning first=102 second=34 amount=1
|
<kerning first="89" second="112" amount="-1" />
|
||||||
kerning first=109 second=39 amount=-3
|
<kerning first="89" second="103" amount="-2" />
|
||||||
kerning first=81 second=84 amount=-1
|
<kerning first="65" second="116" amount="-1" />
|
||||||
kerning first=121 second=97 amount=-1
|
<kerning first="88" second="81" amount="-1" />
|
||||||
kerning first=89 second=99 amount=-2
|
<kerning first="102" second="34" amount="1" />
|
||||||
kerning first=89 second=125 amount=1
|
<kerning first="109" second="39" amount="-3" />
|
||||||
kerning first=81 second=86 amount=-1
|
<kerning first="81" second="84" amount="-1" />
|
||||||
kerning first=114 second=116 amount=2
|
<kerning first="121" second="97" amount="-1" />
|
||||||
kerning first=114 second=119 amount=1
|
<kerning first="89" second="99" amount="-2" />
|
||||||
kerning first=84 second=44 amount=-9
|
<kerning first="89" second="125" amount="1" />
|
||||||
kerning first=102 second=39 amount=1
|
<kerning first="81" second="86" amount="-1" />
|
||||||
kerning first=44 second=34 amount=-10
|
<kerning first="114" second="116" amount="2" />
|
||||||
kerning first=34 second=109 amount=-1
|
<kerning first="114" second="119" amount="1" />
|
||||||
kerning first=84 second=101 amount=-3
|
<kerning first="84" second="44" amount="-9" />
|
||||||
kerning first=75 second=119 amount=-2
|
<kerning first="102" second="39" amount="1" />
|
||||||
kerning first=84 second=81 amount=-1
|
<kerning first="44" second="34" amount="-10" />
|
||||||
kerning first=76 second=121 amount=-4
|
<kerning first="34" second="109" amount="-1" />
|
||||||
kerning first=69 second=101 amount=-1
|
<kerning first="84" second="101" amount="-3" />
|
||||||
kerning first=80 second=90 amount=-1
|
<kerning first="75" second="119" amount="-2" />
|
||||||
kerning first=89 second=97 amount=-2
|
<kerning first="84" second="81" amount="-1" />
|
||||||
kerning first=89 second=109 amount=-1
|
<kerning first="76" second="121" amount="-4" />
|
||||||
kerning first=90 second=99 amount=-1
|
<kerning first="69" second="101" amount="-1" />
|
||||||
kerning first=79 second=88 amount=-1
|
<kerning first="80" second="90" amount="-1" />
|
||||||
kerning first=70 second=103 amount=-1
|
<kerning first="89" second="97" amount="-2" />
|
||||||
kerning first=34 second=103 amount=-2
|
<kerning first="89" second="109" amount="-1" />
|
||||||
kerning first=84 second=67 amount=-1
|
<kerning first="90" second="99" amount="-1" />
|
||||||
kerning first=76 second=79 amount=-2
|
<kerning first="79" second="88" amount="-1" />
|
||||||
kerning first=34 second=113 amount=-2
|
<kerning first="70" second="103" amount="-1" />
|
||||||
kerning first=89 second=41 amount=1
|
<kerning first="34" second="103" amount="-2" />
|
||||||
kerning first=75 second=71 amount=-1
|
<kerning first="84" second="67" amount="-1" />
|
||||||
kerning first=76 second=87 amount=-3
|
<kerning first="76" second="79" amount="-2" />
|
||||||
kerning first=77 second=89 amount=-1
|
<kerning first="34" second="113" amount="-2" />
|
||||||
kerning first=90 second=113 amount=-1
|
<kerning first="89" second="41" amount="1" />
|
||||||
kerning first=118 second=111 amount=-1
|
<kerning first="75" second="71" amount="-1" />
|
||||||
kerning first=118 second=97 amount=-1
|
<kerning first="76" second="87" amount="-3" />
|
||||||
kerning first=88 second=100 amount=-1
|
<kerning first="77" second="89" amount="-1" />
|
||||||
kerning first=89 second=111 amount=-2
|
<kerning first="90" second="113" amount="-1" />
|
||||||
kerning first=90 second=121 amount=-1
|
<kerning first="118" second="111" amount="-1" />
|
||||||
kerning first=89 second=113 amount=-2
|
<kerning first="118" second="97" amount="-1" />
|
||||||
kerning first=84 second=87 amount=1
|
<kerning first="88" second="100" amount="-1" />
|
||||||
kerning first=39 second=111 amount=-3
|
<kerning first="89" second="111" amount="-2" />
|
||||||
kerning first=39 second=100 amount=-2
|
<kerning first="90" second="121" amount="-1" />
|
||||||
kerning first=75 second=113 amount=-1
|
<kerning first="89" second="113" amount="-2" />
|
||||||
kerning first=88 second=111 amount=-1
|
<kerning first="84" second="87" amount="1" />
|
||||||
kerning first=87 second=111 amount=-1
|
<kerning first="39" second="111" amount="-3" />
|
||||||
kerning first=89 second=83 amount=-1
|
<kerning first="39" second="100" amount="-2" />
|
||||||
kerning first=84 second=89 amount=1
|
<kerning first="75" second="113" amount="-1" />
|
||||||
kerning first=84 second=103 amount=-3
|
<kerning first="88" second="111" amount="-1" />
|
||||||
kerning first=70 second=117 amount=-1
|
<kerning first="87" second="111" amount="-1" />
|
||||||
kerning first=67 second=41 amount=-1
|
<kerning first="89" second="83" amount="-1" />
|
||||||
kerning first=89 second=71 amount=-1
|
<kerning first="84" second="89" amount="1" />
|
||||||
kerning first=121 second=44 amount=-6
|
<kerning first="84" second="103" amount="-3" />
|
||||||
kerning first=97 second=121 amount=-1
|
<kerning first="70" second="117" amount="-1" />
|
||||||
kerning first=87 second=113 amount=-1
|
<kerning first="67" second="41" amount="-1" />
|
||||||
kerning first=73 second=84 amount=-1
|
<kerning first="89" second="71" amount="-1" />
|
||||||
kerning first=121 second=46 amount=-6
|
<kerning first="121" second="44" amount="-6" />
|
||||||
kerning first=75 second=99 amount=-1
|
<kerning first="97" second="121" amount="-1" />
|
||||||
kerning first=65 second=112 amount=-2
|
<kerning first="87" second="113" amount="-1" />
|
||||||
kerning first=65 second=85 amount=-1
|
<kerning first="73" second="84" amount="-1" />
|
||||||
kerning first=76 second=67 amount=-2
|
<kerning first="121" second="46" amount="-6" />
|
||||||
kerning first=76 second=81 amount=-2
|
<kerning first="75" second="99" amount="-1" />
|
||||||
kerning first=102 second=100 amount=-1
|
<kerning first="65" second="112" amount="-2" />
|
||||||
kerning first=75 second=79 amount=-1
|
<kerning first="65" second="85" amount="-1" />
|
||||||
kerning first=39 second=65 amount=-4
|
<kerning first="76" second="67" amount="-2" />
|
||||||
kerning first=65 second=84 amount=-4
|
<kerning first="76" second="81" amount="-2" />
|
||||||
kerning first=90 second=101 amount=-1
|
<kerning first="102" second="100" amount="-1" />
|
||||||
kerning first=84 second=121 amount=-3
|
<kerning first="75" second="79" amount="-1" />
|
||||||
kerning first=114 second=39 amount=1
|
<kerning first="39" second="65" amount="-4" />
|
||||||
kerning first=84 second=109 amount=-3
|
<kerning first="65" second="84" amount="-4" />
|
||||||
kerning first=123 second=74 amount=-1
|
<kerning first="90" second="101" amount="-1" />
|
||||||
kerning first=76 second=119 amount=-2
|
<kerning first="84" second="121" amount="-3" />
|
||||||
kerning first=84 second=117 amount=-2
|
<kerning first="114" second="39" amount="1" />
|
||||||
kerning first=76 second=85 amount=-1
|
<kerning first="84" second="109" amount="-3" />
|
||||||
kerning first=76 second=71 amount=-2
|
<kerning first="123" second="74" amount="-1" />
|
||||||
kerning first=79 second=90 amount=-1
|
<kerning first="76" second="119" amount="-2" />
|
||||||
kerning first=107 second=100 amount=-1
|
<kerning first="84" second="117" amount="-2" />
|
||||||
kerning first=90 second=111 amount=-1
|
<kerning first="76" second="85" amount="-1" />
|
||||||
kerning first=79 second=44 amount=-4
|
<kerning first="76" second="71" amount="-2" />
|
||||||
kerning first=75 second=45 amount=-6
|
<kerning first="79" second="90" amount="-1" />
|
||||||
kerning first=79 second=86 amount=-1
|
<kerning first="107" second="100" amount="-1" />
|
||||||
kerning first=79 second=46 amount=-4
|
<kerning first="90" second="111" amount="-1" />
|
||||||
kerning first=76 second=89 amount=-10
|
<kerning first="79" second="44" amount="-4" />
|
||||||
kerning first=68 second=65 amount=-1
|
<kerning first="75" second="45" amount="-6" />
|
||||||
kerning first=79 second=84 amount=-3
|
<kerning first="79" second="86" amount="-1" />
|
||||||
kerning first=87 second=100 amount=-1
|
<kerning first="79" second="46" amount="-4" />
|
||||||
kerning first=84 second=32 amount=-2
|
<kerning first="76" second="89" amount="-10" />
|
||||||
kerning first=90 second=67 amount=-1
|
<kerning first="68" second="65" amount="-1" />
|
||||||
kerning first=69 second=103 amount=-1
|
<kerning first="79" second="84" amount="-3" />
|
||||||
kerning first=90 second=71 amount=-1
|
<kerning first="87" second="100" amount="-1" />
|
||||||
kerning first=86 second=44 amount=-8
|
<kerning first="84" second="32" amount="-2" />
|
||||||
kerning first=69 second=121 amount=-1
|
<kerning first="90" second="67" amount="-1" />
|
||||||
kerning first=87 second=114 amount=-1
|
<kerning first="69" second="103" amount="-1" />
|
||||||
kerning first=118 second=39 amount=1
|
<kerning first="90" second="71" amount="-1" />
|
||||||
kerning first=46 second=39 amount=-10
|
<kerning first="86" second="44" amount="-8" />
|
||||||
kerning first=72 second=84 amount=-1
|
<kerning first="69" second="121" amount="-1" />
|
||||||
kerning first=86 second=46 amount=-8
|
<kerning first="87" second="114" amount="-1" />
|
||||||
kerning first=69 second=113 amount=-1
|
<kerning first="118" second="39" amount="1" />
|
||||||
kerning first=69 second=119 amount=-1
|
<kerning first="46" second="39" amount="-10" />
|
||||||
kerning first=73 second=89 amount=-1
|
<kerning first="72" second="84" amount="-1" />
|
||||||
kerning first=39 second=39 amount=-1
|
<kerning first="86" second="46" amount="-8" />
|
||||||
kerning first=69 second=117 amount=-1
|
<kerning first="69" second="113" amount="-1" />
|
||||||
kerning first=111 second=39 amount=-3
|
<kerning first="69" second="119" amount="-1" />
|
||||||
kerning first=90 second=81 amount=-1
|
<kerning first="73" second="89" amount="-1" />
|
||||||
|
<kerning first="39" second="39" amount="-1" />
|
||||||
|
<kerning first="69" second="117" amount="-1" />
|
||||||
|
<kerning first="111" second="39" amount="-3" />
|
||||||
|
<kerning first="90" second="81" amount="-1" />
|
||||||
|
</kernings>
|
||||||
|
</font>
|
||||||
|
|||||||
@ -1,103 +1,110 @@
|
|||||||
info face="Roboto Mono" size=72 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=1,1,1,1 spacing=-2,-2
|
<?xml version="1.0"?>
|
||||||
common lineHeight=96 base=76 scaleW=512 scaleH=512 pages=1 packed=0
|
<font>
|
||||||
page id=0 file="RobotoMono72White.png"
|
<info face="Roboto Mono" size="72" bold="0" italic="0" charset="" unicode="0" stretchH="100" smooth="1" aa="1" padding="1,1,1,1" spacing="-2,-2" outline="0" />
|
||||||
chars count=98
|
<common lineHeight="96" base="76" scaleW="512" scaleH="512" pages="1" packed="0" alphaChnl="0" redChnl="0" greenChnl="0" blueChnl="0" />
|
||||||
char id=0 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=75 xadvance=0 page=0 chnl=0
|
<pages>
|
||||||
char id=10 x=0 y=0 width=45 height=99 xoffset=-1 yoffset=-2 xadvance=43 page=0 chnl=0
|
<page id="0" file="RobotoMono72White.png" /> </pages>
|
||||||
char id=32 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=75 xadvance=43 page=0 chnl=0
|
<chars count="98">
|
||||||
char id=33 x=498 y=99 width=10 height=55 xoffset=16 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="0" x="0" y="0" width="0" height="0" xoffset="-1" yoffset="75" xadvance="0" page="0" chnl="0" />
|
||||||
char id=34 x=434 y=319 width=20 height=19 xoffset=11 yoffset=21 xadvance=43 page=0 chnl=0
|
<char id="10" x="0" y="0" width="45" height="99" xoffset="-1" yoffset="-2" xadvance="43" page="0" chnl="0" />
|
||||||
char id=35 x=175 y=265 width=41 height=54 xoffset=1 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="32" x="0" y="0" width="0" height="0" xoffset="-1" yoffset="75" xadvance="43" page="0" chnl="0" />
|
||||||
char id=36 x=200 y=0 width=35 height=69 xoffset=5 yoffset=15 xadvance=43 page=0 chnl=0
|
<char id="33" x="498" y="99" width="10" height="55" xoffset="16" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=37 x=0 y=155 width=42 height=56 xoffset=1 yoffset=22 xadvance=44 page=0 chnl=0
|
<char id="34" x="434" y="319" width="20" height="19" xoffset="11" yoffset="21" xadvance="43" page="0" chnl="0" />
|
||||||
char id=38 x=42 y=155 width=41 height=56 xoffset=3 yoffset=22 xadvance=44 page=0 chnl=0
|
<char id="35" x="175" y="265" width="41" height="54" xoffset="1" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=39 x=502 y=211 width=7 height=19 xoffset=16 yoffset=21 xadvance=43 page=0 chnl=0
|
<char id="36" x="200" y="0" width="35" height="69" xoffset="5" yoffset="15" xadvance="43" page="0" chnl="0" />
|
||||||
char id=40 x=45 y=0 width=21 height=78 xoffset=12 yoffset=16 xadvance=44 page=0 chnl=0
|
<char id="37" x="0" y="155" width="42" height="56" xoffset="1" yoffset="22" xadvance="44" page="0" chnl="0" />
|
||||||
char id=41 x=66 y=0 width=22 height=78 xoffset=9 yoffset=16 xadvance=43 page=0 chnl=0
|
<char id="38" x="42" y="155" width="41" height="56" xoffset="3" yoffset="22" xadvance="44" page="0" chnl="0" />
|
||||||
char id=42 x=256 y=319 width=37 height=37 xoffset=4 yoffset=32 xadvance=43 page=0 chnl=0
|
<char id="39" x="502" y="211" width="7" height="19" xoffset="16" yoffset="21" xadvance="43" page="0" chnl="0" />
|
||||||
char id=43 x=219 y=319 width=37 height=40 xoffset=3 yoffset=32 xadvance=43 page=0 chnl=0
|
<char id="40" x="45" y="0" width="21" height="78" xoffset="12" yoffset="16" xadvance="44" page="0" chnl="0" />
|
||||||
char id=44 x=421 y=319 width=13 height=22 xoffset=11 yoffset=67 xadvance=43 page=0 chnl=0
|
<char id="41" x="66" y="0" width="22" height="78" xoffset="9" yoffset="16" xadvance="43" page="0" chnl="0" />
|
||||||
char id=45 x=17 y=360 width=29 height=8 xoffset=7 yoffset=49 xadvance=44 page=0 chnl=0
|
<char id="42" x="256" y="319" width="37" height="37" xoffset="4" yoffset="32" xadvance="43" page="0" chnl="0" />
|
||||||
char id=46 x=496 y=319 width=12 height=13 xoffset=16 yoffset=65 xadvance=43 page=0 chnl=0
|
<char id="43" x="219" y="319" width="37" height="40" xoffset="3" yoffset="32" xadvance="43" page="0" chnl="0" />
|
||||||
char id=47 x=319 y=0 width=31 height=58 xoffset=7 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="44" x="421" y="319" width="13" height="22" xoffset="11" yoffset="67" xadvance="43" page="0" chnl="0" />
|
||||||
char id=48 x=431 y=99 width=35 height=56 xoffset=4 yoffset=22 xadvance=43 page=0 chnl=0
|
<char id="45" x="17" y="360" width="29" height="8" xoffset="7" yoffset="49" xadvance="44" page="0" chnl="0" />
|
||||||
char id=49 x=36 y=265 width=23 height=54 xoffset=6 yoffset=23 xadvance=44 page=0 chnl=0
|
<char id="46" x="496" y="319" width="12" height="13" xoffset="16" yoffset="65" xadvance="43" page="0" chnl="0" />
|
||||||
char id=50 x=189 y=155 width=37 height=55 xoffset=2 yoffset=22 xadvance=44 page=0 chnl=0
|
<char id="47" x="319" y="0" width="31" height="58" xoffset="7" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=51 x=361 y=99 width=35 height=56 xoffset=2 yoffset=22 xadvance=43 page=0 chnl=0
|
<char id="48" x="431" y="99" width="35" height="56" xoffset="4" yoffset="22" xadvance="43" page="0" chnl="0" />
|
||||||
char id=52 x=59 y=265 width=39 height=54 xoffset=2 yoffset=23 xadvance=44 page=0 chnl=0
|
<char id="49" x="36" y="265" width="23" height="54" xoffset="6" yoffset="23" xadvance="44" page="0" chnl="0" />
|
||||||
char id=53 x=226 y=155 width=35 height=55 xoffset=5 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="50" x="189" y="155" width="37" height="55" xoffset="2" yoffset="22" xadvance="44" page="0" chnl="0" />
|
||||||
char id=54 x=261 y=155 width=35 height=55 xoffset=4 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="51" x="361" y="99" width="35" height="56" xoffset="2" yoffset="22" xadvance="43" page="0" chnl="0" />
|
||||||
char id=55 x=98 y=265 width=37 height=54 xoffset=3 yoffset=23 xadvance=44 page=0 chnl=0
|
<char id="52" x="59" y="265" width="39" height="54" xoffset="2" yoffset="23" xadvance="44" page="0" chnl="0" />
|
||||||
char id=56 x=396 y=99 width=35 height=56 xoffset=5 yoffset=22 xadvance=43 page=0 chnl=0
|
<char id="53" x="226" y="155" width="35" height="55" xoffset="5" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=57 x=296 y=155 width=34 height=55 xoffset=4 yoffset=22 xadvance=43 page=0 chnl=0
|
<char id="54" x="261" y="155" width="35" height="55" xoffset="4" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=58 x=490 y=211 width=12 height=43 xoffset=18 yoffset=35 xadvance=43 page=0 chnl=0
|
<char id="55" x="98" y="265" width="37" height="54" xoffset="3" yoffset="23" xadvance="44" page="0" chnl="0" />
|
||||||
char id=59 x=486 y=0 width=14 height=55 xoffset=16 yoffset=35 xadvance=43 page=0 chnl=0
|
<char id="56" x="396" y="99" width="35" height="56" xoffset="5" yoffset="22" xadvance="43" page="0" chnl="0" />
|
||||||
char id=60 x=293 y=319 width=32 height=35 xoffset=5 yoffset=36 xadvance=43 page=0 chnl=0
|
<char id="57" x="296" y="155" width="34" height="55" xoffset="4" yoffset="22" xadvance="43" page="0" chnl="0" />
|
||||||
char id=61 x=388 y=319 width=33 height=23 xoffset=5 yoffset=41 xadvance=43 page=0 chnl=0
|
<char id="58" x="490" y="211" width="12" height="43" xoffset="18" yoffset="35" xadvance="43" page="0" chnl="0" />
|
||||||
char id=62 x=325 y=319 width=33 height=35 xoffset=5 yoffset=36 xadvance=43 page=0 chnl=0
|
<char id="59" x="486" y="0" width="14" height="55" xoffset="16" yoffset="35" xadvance="43" page="0" chnl="0" />
|
||||||
char id=63 x=466 y=99 width=32 height=56 xoffset=6 yoffset=22 xadvance=43 page=0 chnl=0
|
<char id="60" x="293" y="319" width="32" height="35" xoffset="5" yoffset="36" xadvance="43" page="0" chnl="0" />
|
||||||
char id=64 x=135 y=265 width=40 height=54 xoffset=1 yoffset=23 xadvance=42 page=0 chnl=0
|
<char id="61" x="388" y="319" width="33" height="23" xoffset="5" yoffset="41" xadvance="43" page="0" chnl="0" />
|
||||||
char id=65 x=330 y=155 width=42 height=54 xoffset=1 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="62" x="325" y="319" width="33" height="35" xoffset="5" yoffset="36" xadvance="43" page="0" chnl="0" />
|
||||||
char id=66 x=372 y=155 width=35 height=54 xoffset=5 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="63" x="466" y="99" width="32" height="56" xoffset="6" yoffset="22" xadvance="43" page="0" chnl="0" />
|
||||||
char id=67 x=448 y=0 width=38 height=56 xoffset=3 yoffset=22 xadvance=43 page=0 chnl=0
|
<char id="64" x="135" y="265" width="40" height="54" xoffset="1" yoffset="23" xadvance="42" page="0" chnl="0" />
|
||||||
char id=68 x=407 y=155 width=37 height=54 xoffset=4 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="65" x="330" y="155" width="42" height="54" xoffset="1" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=69 x=444 y=155 width=34 height=54 xoffset=5 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="66" x="372" y="155" width="35" height="54" xoffset="5" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=70 x=0 y=211 width=34 height=54 xoffset=6 yoffset=23 xadvance=44 page=0 chnl=0
|
<char id="67" x="448" y="0" width="38" height="56" xoffset="3" yoffset="22" xadvance="43" page="0" chnl="0" />
|
||||||
char id=71 x=0 y=99 width=38 height=56 xoffset=3 yoffset=22 xadvance=44 page=0 chnl=0
|
<char id="68" x="407" y="155" width="37" height="54" xoffset="4" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=72 x=34 y=211 width=36 height=54 xoffset=4 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="69" x="444" y="155" width="34" height="54" xoffset="5" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=73 x=478 y=155 width=33 height=54 xoffset=5 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="70" x="0" y="211" width="34" height="54" xoffset="6" yoffset="23" xadvance="44" page="0" chnl="0" />
|
||||||
char id=74 x=83 y=155 width=36 height=55 xoffset=2 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="71" x="0" y="99" width="38" height="56" xoffset="3" yoffset="22" xadvance="44" page="0" chnl="0" />
|
||||||
char id=75 x=70 y=211 width=38 height=54 xoffset=5 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="72" x="34" y="211" width="36" height="54" xoffset="4" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=76 x=108 y=211 width=34 height=54 xoffset=6 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="73" x="478" y="155" width="33" height="54" xoffset="5" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=77 x=142 y=211 width=36 height=54 xoffset=4 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="74" x="83" y="155" width="36" height="55" xoffset="2" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=78 x=178 y=211 width=35 height=54 xoffset=4 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="75" x="70" y="211" width="38" height="54" xoffset="5" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=79 x=38 y=99 width=38 height=56 xoffset=3 yoffset=22 xadvance=43 page=0 chnl=0
|
<char id="76" x="108" y="211" width="34" height="54" xoffset="6" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=80 x=213 y=211 width=36 height=54 xoffset=6 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="77" x="142" y="211" width="36" height="54" xoffset="4" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=81 x=242 y=0 width=40 height=64 xoffset=2 yoffset=22 xadvance=43 page=0 chnl=0
|
<char id="78" x="178" y="211" width="35" height="54" xoffset="4" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=82 x=249 y=211 width=36 height=54 xoffset=5 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="79" x="38" y="99" width="38" height="56" xoffset="3" yoffset="22" xadvance="43" page="0" chnl="0" />
|
||||||
char id=83 x=76 y=99 width=38 height=56 xoffset=3 yoffset=22 xadvance=44 page=0 chnl=0
|
<char id="80" x="213" y="211" width="36" height="54" xoffset="6" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=84 x=285 y=211 width=40 height=54 xoffset=2 yoffset=23 xadvance=44 page=0 chnl=0
|
<char id="81" x="242" y="0" width="40" height="64" xoffset="2" yoffset="22" xadvance="43" page="0" chnl="0" />
|
||||||
char id=85 x=119 y=155 width=36 height=55 xoffset=4 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="82" x="249" y="211" width="36" height="54" xoffset="5" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=86 x=325 y=211 width=41 height=54 xoffset=1 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="83" x="76" y="99" width="38" height="56" xoffset="3" yoffset="22" xadvance="44" page="0" chnl="0" />
|
||||||
char id=87 x=366 y=211 width=42 height=54 xoffset=1 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="84" x="285" y="211" width="40" height="54" xoffset="2" yoffset="23" xadvance="44" page="0" chnl="0" />
|
||||||
char id=88 x=408 y=211 width=41 height=54 xoffset=2 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="85" x="119" y="155" width="36" height="55" xoffset="4" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=89 x=449 y=211 width=41 height=54 xoffset=1 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="86" x="325" y="211" width="41" height="54" xoffset="1" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=90 x=0 y=265 width=36 height=54 xoffset=3 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="87" x="366" y="211" width="42" height="54" xoffset="1" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=91 x=88 y=0 width=16 height=72 xoffset=14 yoffset=16 xadvance=43 page=0 chnl=0
|
<char id="88" x="408" y="211" width="41" height="54" xoffset="2" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=92 x=350 y=0 width=30 height=58 xoffset=7 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="89" x="449" y="211" width="41" height="54" xoffset="1" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=93 x=104 y=0 width=17 height=72 xoffset=13 yoffset=16 xadvance=44 page=0 chnl=0
|
<char id="90" x="0" y="265" width="36" height="54" xoffset="3" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=94 x=358 y=319 width=30 height=30 xoffset=7 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="91" x="88" y="0" width="16" height="72" xoffset="14" yoffset="16" xadvance="43" page="0" chnl="0" />
|
||||||
char id=95 x=46 y=360 width=34 height=8 xoffset=4 yoffset=74 xadvance=43 page=0 chnl=0
|
<char id="92" x="350" y="0" width="30" height="58" xoffset="7" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=96 x=0 y=360 width=17 height=12 xoffset=13 yoffset=22 xadvance=43 page=0 chnl=0
|
<char id="93" x="104" y="0" width="17" height="72" xoffset="13" yoffset="16" xadvance="44" page="0" chnl="0" />
|
||||||
char id=97 x=251 y=265 width=35 height=42 xoffset=4 yoffset=36 xadvance=43 page=0 chnl=0
|
<char id="94" x="358" y="319" width="30" height="30" xoffset="7" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=98 x=380 y=0 width=34 height=57 xoffset=5 yoffset=21 xadvance=43 page=0 chnl=0
|
<char id="95" x="46" y="360" width="34" height="8" xoffset="4" yoffset="74" xadvance="43" page="0" chnl="0" />
|
||||||
char id=99 x=286 y=265 width=35 height=42 xoffset=4 yoffset=36 xadvance=43 page=0 chnl=0
|
<char id="96" x="0" y="360" width="17" height="12" xoffset="13" yoffset="22" xadvance="43" page="0" chnl="0" />
|
||||||
char id=100 x=414 y=0 width=34 height=57 xoffset=4 yoffset=21 xadvance=43 page=0 chnl=0
|
<char id="97" x="251" y="265" width="35" height="42" xoffset="4" yoffset="36" xadvance="43" page="0" chnl="0" />
|
||||||
char id=101 x=321 y=265 width=36 height=42 xoffset=4 yoffset=36 xadvance=43 page=0 chnl=0
|
<char id="98" x="380" y="0" width="34" height="57" xoffset="5" yoffset="21" xadvance="43" page="0" chnl="0" />
|
||||||
char id=102 x=282 y=0 width=37 height=58 xoffset=4 yoffset=19 xadvance=43 page=0 chnl=0
|
<char id="99" x="286" y="265" width="35" height="42" xoffset="4" yoffset="36" xadvance="43" page="0" chnl="0" />
|
||||||
char id=103 x=114 y=99 width=34 height=56 xoffset=4 yoffset=36 xadvance=43 page=0 chnl=0
|
<char id="100" x="414" y="0" width="34" height="57" xoffset="4" yoffset="21" xadvance="43" page="0" chnl="0" />
|
||||||
char id=104 x=148 y=99 width=34 height=56 xoffset=5 yoffset=21 xadvance=43 page=0 chnl=0
|
<char id="101" x="321" y="265" width="36" height="42" xoffset="4" yoffset="36" xadvance="43" page="0" chnl="0" />
|
||||||
char id=105 x=155 y=155 width=34 height=55 xoffset=6 yoffset=22 xadvance=43 page=0 chnl=0
|
<char id="102" x="282" y="0" width="37" height="58" xoffset="4" yoffset="19" xadvance="43" page="0" chnl="0" />
|
||||||
char id=106 x=121 y=0 width=26 height=71 xoffset=6 yoffset=22 xadvance=44 page=0 chnl=0
|
<char id="103" x="114" y="99" width="34" height="56" xoffset="4" yoffset="36" xadvance="43" page="0" chnl="0" />
|
||||||
char id=107 x=182 y=99 width=36 height=56 xoffset=5 yoffset=21 xadvance=43 page=0 chnl=0
|
<char id="104" x="148" y="99" width="34" height="56" xoffset="5" yoffset="21" xadvance="43" page="0" chnl="0" />
|
||||||
char id=108 x=218 y=99 width=34 height=56 xoffset=6 yoffset=21 xadvance=43 page=0 chnl=0
|
<char id="105" x="155" y="155" width="34" height="55" xoffset="6" yoffset="22" xadvance="43" page="0" chnl="0" />
|
||||||
char id=109 x=428 y=265 width=39 height=41 xoffset=2 yoffset=36 xadvance=43 page=0 chnl=0
|
<char id="106" x="121" y="0" width="26" height="71" xoffset="6" yoffset="22" xadvance="44" page="0" chnl="0" />
|
||||||
char id=110 x=467 y=265 width=34 height=41 xoffset=5 yoffset=36 xadvance=43 page=0 chnl=0
|
<char id="107" x="182" y="99" width="36" height="56" xoffset="5" yoffset="21" xadvance="43" page="0" chnl="0" />
|
||||||
char id=111 x=357 y=265 width=37 height=42 xoffset=3 yoffset=36 xadvance=43 page=0 chnl=0
|
<char id="108" x="218" y="99" width="34" height="56" xoffset="6" yoffset="21" xadvance="43" page="0" chnl="0" />
|
||||||
char id=112 x=252 y=99 width=34 height=56 xoffset=5 yoffset=36 xadvance=43 page=0 chnl=0
|
<char id="109" x="428" y="265" width="39" height="41" xoffset="2" yoffset="36" xadvance="43" page="0" chnl="0" />
|
||||||
char id=113 x=286 y=99 width=34 height=56 xoffset=4 yoffset=36 xadvance=43 page=0 chnl=0
|
<char id="110" x="467" y="265" width="34" height="41" xoffset="5" yoffset="36" xadvance="43" page="0" chnl="0" />
|
||||||
char id=114 x=0 y=319 width=29 height=41 xoffset=11 yoffset=36 xadvance=44 page=0 chnl=0
|
<char id="111" x="357" y="265" width="37" height="42" xoffset="3" yoffset="36" xadvance="43" page="0" chnl="0" />
|
||||||
char id=115 x=394 y=265 width=34 height=42 xoffset=5 yoffset=36 xadvance=43 page=0 chnl=0
|
<char id="112" x="252" y="99" width="34" height="56" xoffset="5" yoffset="36" xadvance="43" page="0" chnl="0" />
|
||||||
char id=116 x=216 y=265 width=35 height=51 xoffset=4 yoffset=27 xadvance=43 page=0 chnl=0
|
<char id="113" x="286" y="99" width="34" height="56" xoffset="4" yoffset="36" xadvance="43" page="0" chnl="0" />
|
||||||
char id=117 x=29 y=319 width=33 height=41 xoffset=5 yoffset=37 xadvance=43 page=0 chnl=0
|
<char id="114" x="0" y="319" width="29" height="41" xoffset="11" yoffset="36" xadvance="44" page="0" chnl="0" />
|
||||||
char id=118 x=62 y=319 width=39 height=40 xoffset=2 yoffset=37 xadvance=43 page=0 chnl=0
|
<char id="115" x="394" y="265" width="34" height="42" xoffset="5" yoffset="36" xadvance="43" page="0" chnl="0" />
|
||||||
char id=119 x=101 y=319 width=43 height=40 xoffset=0 yoffset=37 xadvance=43 page=0 chnl=0
|
<char id="116" x="216" y="265" width="35" height="51" xoffset="4" yoffset="27" xadvance="43" page="0" chnl="0" />
|
||||||
char id=120 x=144 y=319 width=40 height=40 xoffset=2 yoffset=37 xadvance=43 page=0 chnl=0
|
<char id="117" x="29" y="319" width="33" height="41" xoffset="5" yoffset="37" xadvance="43" page="0" chnl="0" />
|
||||||
char id=121 x=320 y=99 width=41 height=56 xoffset=1 yoffset=37 xadvance=43 page=0 chnl=0
|
<char id="118" x="62" y="319" width="39" height="40" xoffset="2" yoffset="37" xadvance="43" page="0" chnl="0" />
|
||||||
char id=122 x=184 y=319 width=35 height=40 xoffset=5 yoffset=37 xadvance=44 page=0 chnl=0
|
<char id="119" x="101" y="319" width="43" height="40" xoffset="0" yoffset="37" xadvance="43" page="0" chnl="0" />
|
||||||
char id=123 x=147 y=0 width=26 height=71 xoffset=10 yoffset=19 xadvance=43 page=0 chnl=0
|
<char id="120" x="144" y="319" width="40" height="40" xoffset="2" yoffset="37" xadvance="43" page="0" chnl="0" />
|
||||||
char id=124 x=235 y=0 width=7 height=68 xoffset=18 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="121" x="320" y="99" width="41" height="56" xoffset="1" yoffset="37" xadvance="43" page="0" chnl="0" />
|
||||||
char id=125 x=173 y=0 width=27 height=71 xoffset=10 yoffset=19 xadvance=44 page=0 chnl=0
|
<char id="122" x="184" y="319" width="35" height="40" xoffset="5" yoffset="37" xadvance="44" page="0" chnl="0" />
|
||||||
char id=126 x=454 y=319 width=42 height=16 xoffset=1 yoffset=47 xadvance=44 page=0 chnl=0
|
<char id="123" x="147" y="0" width="26" height="71" xoffset="10" yoffset="19" xadvance="43" page="0" chnl="0" />
|
||||||
char id=127 x=0 y=0 width=45 height=99 xoffset=-1 yoffset=-2 xadvance=43 page=0 chnl=0
|
<char id="124" x="235" y="0" width="7" height="68" xoffset="18" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
kernings count=0
|
<char id="125" x="173" y="0" width="27" height="71" xoffset="10" yoffset="19" xadvance="44" page="0" chnl="0" />
|
||||||
|
<char id="126" x="454" y="319" width="42" height="16" xoffset="1" yoffset="47" xadvance="44" page="0" chnl="0" />
|
||||||
|
<char id="127" x="0" y="0" width="45" height="99" xoffset="-1" yoffset="-2" xadvance="43" page="0" chnl="0" />
|
||||||
|
</chars>
|
||||||
|
<kernings count="1">
|
||||||
|
<kerning first="0" second="0" amount="0" />
|
||||||
|
</kernings>
|
||||||
|
</font>
|
||||||
|
|||||||
@ -1,492 +1,498 @@
|
|||||||
info face="Roboto Slab Regular" size=72 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=1,1,1,1 spacing=-2,-2
|
<?xml version="1.0"?>
|
||||||
common lineHeight=96 base=76 scaleW=512 scaleH=512 pages=1 packed=0
|
<font>
|
||||||
page id=0 file="RobotoSlab72White.png"
|
<info face="Roboto Slab Regular" size="72" bold="0" italic="0" charset="" unicode="0" stretchH="100" smooth="1" aa="1" padding="1,1,1,1" spacing="-2,-2" outline="0" />
|
||||||
chars count=98
|
<common lineHeight="96" base="76" scaleW="512" scaleH="512" pages="1" packed="0" alphaChnl="0" redChnl="0" greenChnl="0" blueChnl="0" />
|
||||||
char id=0 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=75 xadvance=0 page=0 chnl=0
|
<pages>
|
||||||
char id=10 x=0 y=0 width=70 height=98 xoffset=0 yoffset=-1 xadvance=70 page=0 chnl=0
|
<page id="0" file="RobotoSlab72White.png" /> </pages>
|
||||||
char id=32 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=75 xadvance=18 page=0 chnl=0
|
<chars count="98">
|
||||||
char id=33 x=497 y=156 width=9 height=54 xoffset=4 yoffset=23 xadvance=17 page=0 chnl=0
|
<char id="0" x="0" y="0" width="0" height="0" xoffset="-1" yoffset="75" xadvance="0" page="0" chnl="0" />
|
||||||
char id=34 x=191 y=362 width=19 height=20 xoffset=5 yoffset=20 xadvance=28 page=0 chnl=0
|
<char id="10" x="0" y="0" width="70" height="98" xoffset="0" yoffset="-1" xadvance="70" page="0" chnl="0" />
|
||||||
char id=35 x=406 y=266 width=41 height=54 xoffset=1 yoffset=23 xadvance=43 page=0 chnl=0
|
<char id="32" x="0" y="0" width="0" height="0" xoffset="-1" yoffset="75" xadvance="18" page="0" chnl="0" />
|
||||||
char id=36 x=212 y=0 width=35 height=69 xoffset=2 yoffset=15 xadvance=39 page=0 chnl=0
|
<char id="33" x="497" y="156" width="9" height="54" xoffset="4" yoffset="23" xadvance="17" page="0" chnl="0" />
|
||||||
char id=37 x=174 y=156 width=48 height=56 xoffset=2 yoffset=22 xadvance=52 page=0 chnl=0
|
<char id="34" x="191" y="362" width="19" height="20" xoffset="5" yoffset="20" xadvance="28" page="0" chnl="0" />
|
||||||
char id=38 x=222 y=156 width=44 height=56 xoffset=2 yoffset=22 xadvance=46 page=0 chnl=0
|
<char id="35" x="406" y="266" width="41" height="54" xoffset="1" yoffset="23" xadvance="43" page="0" chnl="0" />
|
||||||
char id=39 x=210 y=362 width=8 height=20 xoffset=5 yoffset=20 xadvance=17 page=0 chnl=0
|
<char id="36" x="212" y="0" width="35" height="69" xoffset="2" yoffset="15" xadvance="39" page="0" chnl="0" />
|
||||||
char id=40 x=70 y=0 width=21 height=77 xoffset=3 yoffset=17 xadvance=23 page=0 chnl=0
|
<char id="37" x="174" y="156" width="48" height="56" xoffset="2" yoffset="22" xadvance="52" page="0" chnl="0" />
|
||||||
char id=41 x=91 y=0 width=21 height=77 xoffset=-1 yoffset=17 xadvance=23 page=0 chnl=0
|
<char id="38" x="222" y="156" width="44" height="56" xoffset="2" yoffset="22" xadvance="46" page="0" chnl="0" />
|
||||||
char id=42 x=100 y=362 width=31 height=33 xoffset=1 yoffset=23 xadvance=33 page=0 chnl=0
|
<char id="39" x="210" y="362" width="8" height="20" xoffset="5" yoffset="20" xadvance="17" page="0" chnl="0" />
|
||||||
char id=43 x=0 y=362 width=37 height=40 xoffset=2 yoffset=32 xadvance=41 page=0 chnl=0
|
<char id="40" x="70" y="0" width="21" height="77" xoffset="3" yoffset="17" xadvance="23" page="0" chnl="0" />
|
||||||
char id=44 x=492 y=320 width=13 height=21 xoffset=-1 yoffset=67 xadvance=14 page=0 chnl=0
|
<char id="41" x="91" y="0" width="21" height="77" xoffset="-1" yoffset="17" xadvance="23" page="0" chnl="0" />
|
||||||
char id=45 x=287 y=362 width=19 height=8 xoffset=4 yoffset=50 xadvance=27 page=0 chnl=0
|
<char id="42" x="100" y="362" width="31" height="33" xoffset="1" yoffset="23" xadvance="33" page="0" chnl="0" />
|
||||||
char id=46 x=278 y=362 width=9 height=9 xoffset=4 yoffset=68 xadvance=17 page=0 chnl=0
|
<char id="43" x="0" y="362" width="37" height="40" xoffset="2" yoffset="32" xadvance="41" page="0" chnl="0" />
|
||||||
char id=47 x=470 y=0 width=30 height=58 xoffset=-1 yoffset=23 xadvance=29 page=0 chnl=0
|
<char id="44" x="492" y="320" width="13" height="21" xoffset="-1" yoffset="67" xadvance="14" page="0" chnl="0" />
|
||||||
char id=48 x=139 y=156 width=35 height=56 xoffset=3 yoffset=22 xadvance=41 page=0 chnl=0
|
<char id="45" x="287" y="362" width="19" height="8" xoffset="4" yoffset="50" xadvance="27" page="0" chnl="0" />
|
||||||
char id=49 x=305 y=266 width=25 height=54 xoffset=3 yoffset=23 xadvance=30 page=0 chnl=0
|
<char id="46" x="278" y="362" width="9" height="9" xoffset="4" yoffset="68" xadvance="17" page="0" chnl="0" />
|
||||||
char id=50 x=357 y=156 width=36 height=55 xoffset=2 yoffset=22 xadvance=40 page=0 chnl=0
|
<char id="47" x="470" y="0" width="30" height="58" xoffset="-1" yoffset="23" xadvance="29" page="0" chnl="0" />
|
||||||
char id=51 x=0 y=156 width=34 height=56 xoffset=2 yoffset=22 xadvance=39 page=0 chnl=0
|
<char id="48" x="139" y="156" width="35" height="56" xoffset="3" yoffset="22" xadvance="41" page="0" chnl="0" />
|
||||||
char id=52 x=330 y=266 width=39 height=54 xoffset=1 yoffset=23 xadvance=42 page=0 chnl=0
|
<char id="49" x="305" y="266" width="25" height="54" xoffset="3" yoffset="23" xadvance="30" page="0" chnl="0" />
|
||||||
char id=53 x=393 y=156 width=33 height=55 xoffset=2 yoffset=23 xadvance=37 page=0 chnl=0
|
<char id="50" x="357" y="156" width="36" height="55" xoffset="2" yoffset="22" xadvance="40" page="0" chnl="0" />
|
||||||
char id=54 x=34 y=156 width=35 height=56 xoffset=3 yoffset=22 xadvance=40 page=0 chnl=0
|
<char id="51" x="0" y="156" width="34" height="56" xoffset="2" yoffset="22" xadvance="39" page="0" chnl="0" />
|
||||||
char id=55 x=369 y=266 width=37 height=54 xoffset=2 yoffset=23 xadvance=40 page=0 chnl=0
|
<char id="52" x="330" y="266" width="39" height="54" xoffset="1" yoffset="23" xadvance="42" page="0" chnl="0" />
|
||||||
char id=56 x=69 y=156 width=35 height=56 xoffset=2 yoffset=22 xadvance=39 page=0 chnl=0
|
<char id="53" x="393" y="156" width="33" height="55" xoffset="2" yoffset="23" xadvance="37" page="0" chnl="0" />
|
||||||
char id=57 x=104 y=156 width=35 height=56 xoffset=2 yoffset=22 xadvance=41 page=0 chnl=0
|
<char id="54" x="34" y="156" width="35" height="56" xoffset="3" yoffset="22" xadvance="40" page="0" chnl="0" />
|
||||||
char id=58 x=500 y=0 width=9 height=40 xoffset=4 yoffset=37 xadvance=15 page=0 chnl=0
|
<char id="55" x="369" y="266" width="37" height="54" xoffset="2" yoffset="23" xadvance="40" page="0" chnl="0" />
|
||||||
char id=59 x=447 y=266 width=13 height=52 xoffset=0 yoffset=37 xadvance=15 page=0 chnl=0
|
<char id="56" x="69" y="156" width="35" height="56" xoffset="2" yoffset="22" xadvance="39" page="0" chnl="0" />
|
||||||
char id=60 x=37 y=362 width=31 height=35 xoffset=2 yoffset=39 xadvance=36 page=0 chnl=0
|
<char id="57" x="104" y="156" width="35" height="56" xoffset="2" yoffset="22" xadvance="41" page="0" chnl="0" />
|
||||||
char id=61 x=160 y=362 width=31 height=23 xoffset=4 yoffset=40 xadvance=39 page=0 chnl=0
|
<char id="58" x="500" y="0" width="9" height="40" xoffset="4" yoffset="37" xadvance="15" page="0" chnl="0" />
|
||||||
char id=62 x=68 y=362 width=32 height=35 xoffset=3 yoffset=39 xadvance=37 page=0 chnl=0
|
<char id="59" x="447" y="266" width="13" height="52" xoffset="0" yoffset="37" xadvance="15" page="0" chnl="0" />
|
||||||
char id=63 x=480 y=98 width=31 height=55 xoffset=1 yoffset=22 xadvance=33 page=0 chnl=0
|
<char id="60" x="37" y="362" width="31" height="35" xoffset="2" yoffset="39" xadvance="36" page="0" chnl="0" />
|
||||||
char id=64 x=247 y=0 width=60 height=68 xoffset=1 yoffset=25 xadvance=64 page=0 chnl=0
|
<char id="61" x="160" y="362" width="31" height="23" xoffset="4" yoffset="40" xadvance="39" page="0" chnl="0" />
|
||||||
char id=65 x=426 y=156 width=51 height=54 xoffset=1 yoffset=23 xadvance=53 page=0 chnl=0
|
<char id="62" x="68" y="362" width="32" height="35" xoffset="3" yoffset="39" xadvance="37" page="0" chnl="0" />
|
||||||
char id=66 x=0 y=212 width=44 height=54 xoffset=1 yoffset=23 xadvance=47 page=0 chnl=0
|
<char id="63" x="480" y="98" width="31" height="55" xoffset="1" yoffset="22" xadvance="33" page="0" chnl="0" />
|
||||||
char id=67 x=191 y=98 width=42 height=56 xoffset=1 yoffset=22 xadvance=46 page=0 chnl=0
|
<char id="64" x="247" y="0" width="60" height="68" xoffset="1" yoffset="25" xadvance="64" page="0" chnl="0" />
|
||||||
char id=68 x=44 y=212 width=46 height=54 xoffset=1 yoffset=23 xadvance=50 page=0 chnl=0
|
<char id="65" x="426" y="156" width="51" height="54" xoffset="1" yoffset="23" xadvance="53" page="0" chnl="0" />
|
||||||
char id=69 x=90 y=212 width=42 height=54 xoffset=1 yoffset=23 xadvance=46 page=0 chnl=0
|
<char id="66" x="0" y="212" width="44" height="54" xoffset="1" yoffset="23" xadvance="47" page="0" chnl="0" />
|
||||||
char id=70 x=132 y=212 width=42 height=54 xoffset=1 yoffset=23 xadvance=44 page=0 chnl=0
|
<char id="67" x="191" y="98" width="42" height="56" xoffset="1" yoffset="22" xadvance="46" page="0" chnl="0" />
|
||||||
char id=71 x=233 y=98 width=43 height=56 xoffset=1 yoffset=22 xadvance=49 page=0 chnl=0
|
<char id="68" x="44" y="212" width="46" height="54" xoffset="1" yoffset="23" xadvance="50" page="0" chnl="0" />
|
||||||
char id=72 x=174 y=212 width=52 height=54 xoffset=1 yoffset=23 xadvance=55 page=0 chnl=0
|
<char id="69" x="90" y="212" width="42" height="54" xoffset="1" yoffset="23" xadvance="46" page="0" chnl="0" />
|
||||||
char id=73 x=477 y=156 width=20 height=54 xoffset=1 yoffset=23 xadvance=22 page=0 chnl=0
|
<char id="70" x="132" y="212" width="42" height="54" xoffset="1" yoffset="23" xadvance="44" page="0" chnl="0" />
|
||||||
char id=74 x=266 y=156 width=39 height=55 xoffset=1 yoffset=23 xadvance=41 page=0 chnl=0
|
<char id="71" x="233" y="98" width="43" height="56" xoffset="1" yoffset="22" xadvance="49" page="0" chnl="0" />
|
||||||
char id=75 x=226 y=212 width=48 height=54 xoffset=1 yoffset=23 xadvance=50 page=0 chnl=0
|
<char id="72" x="174" y="212" width="52" height="54" xoffset="1" yoffset="23" xadvance="55" page="0" chnl="0" />
|
||||||
char id=76 x=274 y=212 width=39 height=54 xoffset=1 yoffset=23 xadvance=42 page=0 chnl=0
|
<char id="73" x="477" y="156" width="20" height="54" xoffset="1" yoffset="23" xadvance="22" page="0" chnl="0" />
|
||||||
char id=77 x=313 y=212 width=64 height=54 xoffset=1 yoffset=23 xadvance=66 page=0 chnl=0
|
<char id="74" x="266" y="156" width="39" height="55" xoffset="1" yoffset="23" xadvance="41" page="0" chnl="0" />
|
||||||
char id=78 x=377 y=212 width=52 height=54 xoffset=1 yoffset=23 xadvance=54 page=0 chnl=0
|
<char id="75" x="226" y="212" width="48" height="54" xoffset="1" yoffset="23" xadvance="50" page="0" chnl="0" />
|
||||||
char id=79 x=276 y=98 width=47 height=56 xoffset=2 yoffset=22 xadvance=51 page=0 chnl=0
|
<char id="76" x="274" y="212" width="39" height="54" xoffset="1" yoffset="23" xadvance="42" page="0" chnl="0" />
|
||||||
char id=80 x=429 y=212 width=43 height=54 xoffset=1 yoffset=23 xadvance=45 page=0 chnl=0
|
<char id="77" x="313" y="212" width="64" height="54" xoffset="1" yoffset="23" xadvance="66" page="0" chnl="0" />
|
||||||
char id=81 x=307 y=0 width=48 height=64 xoffset=2 yoffset=22 xadvance=51 page=0 chnl=0
|
<char id="78" x="377" y="212" width="52" height="54" xoffset="1" yoffset="23" xadvance="54" page="0" chnl="0" />
|
||||||
char id=82 x=0 y=266 width=46 height=54 xoffset=1 yoffset=23 xadvance=48 page=0 chnl=0
|
<char id="79" x="276" y="98" width="47" height="56" xoffset="2" yoffset="22" xadvance="51" page="0" chnl="0" />
|
||||||
char id=83 x=323 y=98 width=38 height=56 xoffset=3 yoffset=22 xadvance=43 page=0 chnl=0
|
<char id="80" x="429" y="212" width="43" height="54" xoffset="1" yoffset="23" xadvance="45" page="0" chnl="0" />
|
||||||
char id=84 x=46 y=266 width=45 height=54 xoffset=0 yoffset=23 xadvance=45 page=0 chnl=0
|
<char id="81" x="307" y="0" width="48" height="64" xoffset="2" yoffset="22" xadvance="51" page="0" chnl="0" />
|
||||||
char id=85 x=305 y=156 width=52 height=55 xoffset=1 yoffset=23 xadvance=54 page=0 chnl=0
|
<char id="82" x="0" y="266" width="46" height="54" xoffset="1" yoffset="23" xadvance="48" page="0" chnl="0" />
|
||||||
char id=86 x=91 y=266 width=50 height=54 xoffset=1 yoffset=23 xadvance=52 page=0 chnl=0
|
<char id="83" x="323" y="98" width="38" height="56" xoffset="3" yoffset="22" xadvance="43" page="0" chnl="0" />
|
||||||
char id=87 x=141 y=266 width=67 height=54 xoffset=0 yoffset=23 xadvance=67 page=0 chnl=0
|
<char id="84" x="46" y="266" width="45" height="54" xoffset="0" yoffset="23" xadvance="45" page="0" chnl="0" />
|
||||||
char id=88 x=208 y=266 width=49 height=54 xoffset=1 yoffset=23 xadvance=51 page=0 chnl=0
|
<char id="85" x="305" y="156" width="52" height="55" xoffset="1" yoffset="23" xadvance="54" page="0" chnl="0" />
|
||||||
char id=89 x=257 y=266 width=48 height=54 xoffset=1 yoffset=23 xadvance=50 page=0 chnl=0
|
<char id="86" x="91" y="266" width="50" height="54" xoffset="1" yoffset="23" xadvance="52" page="0" chnl="0" />
|
||||||
char id=90 x=472 y=212 width=38 height=54 xoffset=2 yoffset=23 xadvance=42 page=0 chnl=0
|
<char id="87" x="141" y="266" width="67" height="54" xoffset="0" yoffset="23" xadvance="67" page="0" chnl="0" />
|
||||||
char id=91 x=180 y=0 width=16 height=72 xoffset=5 yoffset=16 xadvance=21 page=0 chnl=0
|
<char id="88" x="208" y="266" width="49" height="54" xoffset="1" yoffset="23" xadvance="51" page="0" chnl="0" />
|
||||||
char id=92 x=0 y=98 width=31 height=58 xoffset=0 yoffset=23 xadvance=30 page=0 chnl=0
|
<char id="89" x="257" y="266" width="48" height="54" xoffset="1" yoffset="23" xadvance="50" page="0" chnl="0" />
|
||||||
char id=93 x=196 y=0 width=16 height=72 xoffset=-1 yoffset=16 xadvance=19 page=0 chnl=0
|
<char id="90" x="472" y="212" width="38" height="54" xoffset="2" yoffset="23" xadvance="42" page="0" chnl="0" />
|
||||||
char id=94 x=131 y=362 width=29 height=28 xoffset=1 yoffset=23 xadvance=30 page=0 chnl=0
|
<char id="91" x="180" y="0" width="16" height="72" xoffset="5" yoffset="16" xadvance="21" page="0" chnl="0" />
|
||||||
char id=95 x=306 y=362 width=34 height=8 xoffset=3 yoffset=74 xadvance=40 page=0 chnl=0
|
<char id="92" x="0" y="98" width="31" height="58" xoffset="0" yoffset="23" xadvance="30" page="0" chnl="0" />
|
||||||
char id=96 x=260 y=362 width=18 height=12 xoffset=1 yoffset=22 xadvance=20 page=0 chnl=0
|
<char id="93" x="196" y="0" width="16" height="72" xoffset="-1" yoffset="16" xadvance="19" page="0" chnl="0" />
|
||||||
char id=97 x=0 y=320 width=36 height=42 xoffset=3 yoffset=36 xadvance=41 page=0 chnl=0
|
<char id="94" x="131" y="362" width="29" height="28" xoffset="1" yoffset="23" xadvance="30" page="0" chnl="0" />
|
||||||
char id=98 x=363 y=0 width=41 height=58 xoffset=-2 yoffset=20 xadvance=42 page=0 chnl=0
|
<char id="95" x="306" y="362" width="34" height="8" xoffset="3" yoffset="74" xadvance="40" page="0" chnl="0" />
|
||||||
char id=99 x=36 y=320 width=34 height=42 xoffset=2 yoffset=36 xadvance=39 page=0 chnl=0
|
<char id="96" x="260" y="362" width="18" height="12" xoffset="1" yoffset="22" xadvance="20" page="0" chnl="0" />
|
||||||
char id=100 x=404 y=0 width=40 height=58 xoffset=2 yoffset=20 xadvance=43 page=0 chnl=0
|
<char id="97" x="0" y="320" width="36" height="42" xoffset="3" yoffset="36" xadvance="41" page="0" chnl="0" />
|
||||||
char id=101 x=70 y=320 width=34 height=42 xoffset=2 yoffset=36 xadvance=39 page=0 chnl=0
|
<char id="98" x="363" y="0" width="41" height="58" xoffset="-2" yoffset="20" xadvance="42" page="0" chnl="0" />
|
||||||
char id=102 x=444 y=0 width=26 height=58 xoffset=1 yoffset=19 xadvance=25 page=0 chnl=0
|
<char id="99" x="36" y="320" width="34" height="42" xoffset="2" yoffset="36" xadvance="39" page="0" chnl="0" />
|
||||||
char id=103 x=31 y=98 width=34 height=57 xoffset=2 yoffset=36 xadvance=40 page=0 chnl=0
|
<char id="100" x="404" y="0" width="40" height="58" xoffset="2" yoffset="20" xadvance="43" page="0" chnl="0" />
|
||||||
char id=104 x=65 y=98 width=44 height=57 xoffset=1 yoffset=20 xadvance=46 page=0 chnl=0
|
<char id="101" x="70" y="320" width="34" height="42" xoffset="2" yoffset="36" xadvance="39" page="0" chnl="0" />
|
||||||
char id=105 x=109 y=98 width=20 height=57 xoffset=2 yoffset=20 xadvance=23 page=0 chnl=0
|
<char id="102" x="444" y="0" width="26" height="58" xoffset="1" yoffset="19" xadvance="25" page="0" chnl="0" />
|
||||||
char id=106 x=112 y=0 width=18 height=73 xoffset=-2 yoffset=20 xadvance=20 page=0 chnl=0
|
<char id="103" x="31" y="98" width="34" height="57" xoffset="2" yoffset="36" xadvance="40" page="0" chnl="0" />
|
||||||
char id=107 x=129 y=98 width=42 height=57 xoffset=1 yoffset=20 xadvance=44 page=0 chnl=0
|
<char id="104" x="65" y="98" width="44" height="57" xoffset="1" yoffset="20" xadvance="46" page="0" chnl="0" />
|
||||||
char id=108 x=171 y=98 width=20 height=57 xoffset=1 yoffset=20 xadvance=22 page=0 chnl=0
|
<char id="105" x="109" y="98" width="20" height="57" xoffset="2" yoffset="20" xadvance="23" page="0" chnl="0" />
|
||||||
char id=109 x=171 y=320 width=66 height=41 xoffset=1 yoffset=36 xadvance=68 page=0 chnl=0
|
<char id="106" x="112" y="0" width="18" height="73" xoffset="-2" yoffset="20" xadvance="20" page="0" chnl="0" />
|
||||||
char id=110 x=237 y=320 width=44 height=41 xoffset=1 yoffset=36 xadvance=46 page=0 chnl=0
|
<char id="107" x="129" y="98" width="42" height="57" xoffset="1" yoffset="20" xadvance="44" page="0" chnl="0" />
|
||||||
char id=111 x=104 y=320 width=36 height=42 xoffset=2 yoffset=36 xadvance=40 page=0 chnl=0
|
<char id="108" x="171" y="98" width="20" height="57" xoffset="1" yoffset="20" xadvance="22" page="0" chnl="0" />
|
||||||
char id=112 x=361 y=98 width=40 height=56 xoffset=1 yoffset=36 xadvance=43 page=0 chnl=0
|
<char id="109" x="171" y="320" width="66" height="41" xoffset="1" yoffset="36" xadvance="68" page="0" chnl="0" />
|
||||||
char id=113 x=401 y=98 width=39 height=56 xoffset=2 yoffset=36 xadvance=40 page=0 chnl=0
|
<char id="110" x="237" y="320" width="44" height="41" xoffset="1" yoffset="36" xadvance="46" page="0" chnl="0" />
|
||||||
char id=114 x=484 y=266 width=27 height=41 xoffset=2 yoffset=36 xadvance=30 page=0 chnl=0
|
<char id="111" x="104" y="320" width="36" height="42" xoffset="2" yoffset="36" xadvance="40" page="0" chnl="0" />
|
||||||
char id=115 x=140 y=320 width=31 height=42 xoffset=3 yoffset=36 xadvance=36 page=0 chnl=0
|
<char id="112" x="361" y="98" width="40" height="56" xoffset="1" yoffset="36" xadvance="43" page="0" chnl="0" />
|
||||||
char id=116 x=460 y=266 width=24 height=51 xoffset=1 yoffset=27 xadvance=26 page=0 chnl=0
|
<char id="113" x="401" y="98" width="39" height="56" xoffset="2" yoffset="36" xadvance="40" page="0" chnl="0" />
|
||||||
char id=117 x=281 y=320 width=43 height=41 xoffset=0 yoffset=37 xadvance=44 page=0 chnl=0
|
<char id="114" x="484" y="266" width="27" height="41" xoffset="2" yoffset="36" xadvance="30" page="0" chnl="0" />
|
||||||
char id=118 x=324 y=320 width=39 height=40 xoffset=0 yoffset=37 xadvance=40 page=0 chnl=0
|
<char id="115" x="140" y="320" width="31" height="42" xoffset="3" yoffset="36" xadvance="36" page="0" chnl="0" />
|
||||||
char id=119 x=363 y=320 width=57 height=40 xoffset=1 yoffset=37 xadvance=59 page=0 chnl=0
|
<char id="116" x="460" y="266" width="24" height="51" xoffset="1" yoffset="27" xadvance="26" page="0" chnl="0" />
|
||||||
char id=120 x=420 y=320 width=40 height=40 xoffset=1 yoffset=37 xadvance=42 page=0 chnl=0
|
<char id="117" x="281" y="320" width="43" height="41" xoffset="0" yoffset="37" xadvance="44" page="0" chnl="0" />
|
||||||
char id=121 x=440 y=98 width=40 height=56 xoffset=0 yoffset=37 xadvance=41 page=0 chnl=0
|
<char id="118" x="324" y="320" width="39" height="40" xoffset="0" yoffset="37" xadvance="40" page="0" chnl="0" />
|
||||||
char id=122 x=460 y=320 width=32 height=40 xoffset=3 yoffset=37 xadvance=38 page=0 chnl=0
|
<char id="119" x="363" y="320" width="57" height="40" xoffset="1" yoffset="37" xadvance="59" page="0" chnl="0" />
|
||||||
char id=123 x=130 y=0 width=25 height=73 xoffset=1 yoffset=18 xadvance=25 page=0 chnl=0
|
<char id="120" x="420" y="320" width="40" height="40" xoffset="1" yoffset="37" xadvance="42" page="0" chnl="0" />
|
||||||
char id=124 x=355 y=0 width=8 height=63 xoffset=4 yoffset=23 xadvance=16 page=0 chnl=0
|
<char id="121" x="440" y="98" width="40" height="56" xoffset="0" yoffset="37" xadvance="41" page="0" chnl="0" />
|
||||||
char id=125 x=155 y=0 width=25 height=73 xoffset=-1 yoffset=18 xadvance=25 page=0 chnl=0
|
<char id="122" x="460" y="320" width="32" height="40" xoffset="3" yoffset="37" xadvance="38" page="0" chnl="0" />
|
||||||
char id=126 x=218 y=362 width=42 height=16 xoffset=3 yoffset=47 xadvance=49 page=0 chnl=0
|
<char id="123" x="130" y="0" width="25" height="73" xoffset="1" yoffset="18" xadvance="25" page="0" chnl="0" />
|
||||||
char id=127 x=0 y=0 width=70 height=98 xoffset=0 yoffset=-1 xadvance=70 page=0 chnl=0
|
<char id="124" x="355" y="0" width="8" height="63" xoffset="4" yoffset="23" xadvance="16" page="0" chnl="0" />
|
||||||
kernings count=389
|
<char id="125" x="155" y="0" width="25" height="73" xoffset="-1" yoffset="18" xadvance="25" page="0" chnl="0" />
|
||||||
kerning first=86 second=45 amount=-1
|
<char id="126" x="218" y="362" width="42" height="16" xoffset="3" yoffset="47" xadvance="49" page="0" chnl="0" />
|
||||||
kerning first=114 second=46 amount=-4
|
<char id="127" x="0" y="0" width="70" height="98" xoffset="0" yoffset="-1" xadvance="70" page="0" chnl="0" />
|
||||||
kerning first=40 second=87 amount=1
|
</chars>
|
||||||
kerning first=70 second=99 amount=-1
|
<kernings count="389">
|
||||||
kerning first=84 second=110 amount=-3
|
<kerning first="86" second="45" amount="-1" />
|
||||||
kerning first=114 second=116 amount=1
|
<kerning first="114" second="46" amount="-4" />
|
||||||
kerning first=39 second=65 amount=-4
|
<kerning first="40" second="87" amount="1" />
|
||||||
kerning first=104 second=34 amount=-1
|
<kerning first="70" second="99" amount="-1" />
|
||||||
kerning first=89 second=71 amount=-1
|
<kerning first="84" second="110" amount="-3" />
|
||||||
kerning first=107 second=113 amount=-1
|
<kerning first="114" second="116" amount="1" />
|
||||||
kerning first=78 second=88 amount=1
|
<kerning first="39" second="65" amount="-4" />
|
||||||
kerning first=109 second=39 amount=-1
|
<kerning first="104" second="34" amount="-1" />
|
||||||
kerning first=120 second=100 amount=-1
|
<kerning first="89" second="71" amount="-1" />
|
||||||
kerning first=84 second=100 amount=-3
|
<kerning first="107" second="113" amount="-1" />
|
||||||
kerning first=68 second=90 amount=-1
|
<kerning first="78" second="88" amount="1" />
|
||||||
kerning first=68 second=44 amount=-4
|
<kerning first="109" second="39" amount="-1" />
|
||||||
kerning first=84 second=103 amount=-3
|
<kerning first="120" second="100" amount="-1" />
|
||||||
kerning first=34 second=97 amount=-2
|
<kerning first="84" second="100" amount="-3" />
|
||||||
kerning first=70 second=97 amount=-1
|
<kerning first="68" second="90" amount="-1" />
|
||||||
kerning first=76 second=81 amount=-2
|
<kerning first="68" second="44" amount="-4" />
|
||||||
kerning first=73 second=89 amount=-1
|
<kerning first="84" second="103" amount="-3" />
|
||||||
kerning first=84 second=44 amount=-8
|
<kerning first="34" second="97" amount="-2" />
|
||||||
kerning first=68 second=65 amount=-3
|
<kerning first="70" second="97" amount="-1" />
|
||||||
kerning first=97 second=34 amount=-2
|
<kerning first="76" second="81" amount="-2" />
|
||||||
kerning first=111 second=121 amount=-1
|
<kerning first="73" second="89" amount="-1" />
|
||||||
kerning first=79 second=90 amount=-1
|
<kerning first="84" second="44" amount="-8" />
|
||||||
kerning first=75 second=121 amount=-1
|
<kerning first="68" second="65" amount="-3" />
|
||||||
kerning first=75 second=118 amount=-1
|
<kerning first="97" second="34" amount="-2" />
|
||||||
kerning first=111 second=118 amount=-1
|
<kerning first="111" second="121" amount="-1" />
|
||||||
kerning first=89 second=65 amount=-9
|
<kerning first="79" second="90" amount="-1" />
|
||||||
kerning first=75 second=71 amount=-4
|
<kerning first="75" second="121" amount="-1" />
|
||||||
kerning first=39 second=99 amount=-2
|
<kerning first="75" second="118" amount="-1" />
|
||||||
kerning first=75 second=99 amount=-1
|
<kerning first="111" second="118" amount="-1" />
|
||||||
kerning first=90 second=121 amount=-1
|
<kerning first="89" second="65" amount="-9" />
|
||||||
kerning first=44 second=39 amount=-6
|
<kerning first="75" second="71" amount="-4" />
|
||||||
kerning first=89 second=46 amount=-7
|
<kerning first="39" second="99" amount="-2" />
|
||||||
kerning first=89 second=74 amount=-7
|
<kerning first="75" second="99" amount="-1" />
|
||||||
kerning first=34 second=103 amount=-2
|
<kerning first="90" second="121" amount="-1" />
|
||||||
kerning first=70 second=103 amount=-1
|
<kerning first="44" second="39" amount="-6" />
|
||||||
kerning first=112 second=39 amount=-1
|
<kerning first="89" second="46" amount="-7" />
|
||||||
kerning first=122 second=113 amount=-1
|
<kerning first="89" second="74" amount="-7" />
|
||||||
kerning first=86 second=113 amount=-2
|
<kerning first="34" second="103" amount="-2" />
|
||||||
kerning first=68 second=84 amount=-1
|
<kerning first="70" second="103" amount="-1" />
|
||||||
kerning first=89 second=110 amount=-1
|
<kerning first="112" second="39" amount="-1" />
|
||||||
kerning first=34 second=100 amount=-2
|
<kerning first="122" second="113" amount="-1" />
|
||||||
kerning first=68 second=86 amount=-1
|
<kerning first="86" second="113" amount="-2" />
|
||||||
kerning first=87 second=45 amount=-2
|
<kerning first="68" second="84" amount="-1" />
|
||||||
kerning first=39 second=34 amount=-4
|
<kerning first="89" second="110" amount="-1" />
|
||||||
kerning first=114 second=100 amount=-1
|
<kerning first="34" second="100" amount="-2" />
|
||||||
kerning first=84 second=81 amount=-1
|
<kerning first="68" second="86" amount="-1" />
|
||||||
kerning first=70 second=101 amount=-1
|
<kerning first="87" second="45" amount="-2" />
|
||||||
kerning first=68 second=89 amount=-2
|
<kerning first="39" second="34" amount="-4" />
|
||||||
kerning first=88 second=117 amount=-1
|
<kerning first="114" second="100" amount="-1" />
|
||||||
kerning first=112 second=34 amount=-1
|
<kerning first="84" second="81" amount="-1" />
|
||||||
kerning first=76 second=67 amount=-2
|
<kerning first="70" second="101" amount="-1" />
|
||||||
kerning first=76 second=34 amount=-5
|
<kerning first="68" second="89" amount="-2" />
|
||||||
kerning first=88 second=111 amount=-1
|
<kerning first="88" second="117" amount="-1" />
|
||||||
kerning first=66 second=86 amount=-1
|
<kerning first="112" second="34" amount="-1" />
|
||||||
kerning first=66 second=89 amount=-2
|
<kerning first="76" second="67" amount="-2" />
|
||||||
kerning first=122 second=101 amount=-1
|
<kerning first="76" second="34" amount="-5" />
|
||||||
kerning first=86 second=101 amount=-2
|
<kerning first="88" second="111" amount="-1" />
|
||||||
kerning first=76 second=121 amount=-5
|
<kerning first="66" second="86" amount="-1" />
|
||||||
kerning first=84 second=119 amount=-2
|
<kerning first="66" second="89" amount="-2" />
|
||||||
kerning first=84 second=112 amount=-3
|
<kerning first="122" second="101" amount="-1" />
|
||||||
kerning first=87 second=111 amount=-1
|
<kerning first="86" second="101" amount="-2" />
|
||||||
kerning first=69 second=118 amount=-1
|
<kerning first="76" second="121" amount="-5" />
|
||||||
kerning first=65 second=117 amount=-2
|
<kerning first="84" second="119" amount="-2" />
|
||||||
kerning first=65 second=89 amount=-9
|
<kerning first="84" second="112" amount="-3" />
|
||||||
kerning first=72 second=89 amount=-1
|
<kerning first="87" second="111" amount="-1" />
|
||||||
kerning first=119 second=44 amount=-4
|
<kerning first="69" second="118" amount="-1" />
|
||||||
kerning first=69 second=121 amount=-1
|
<kerning first="65" second="117" amount="-2" />
|
||||||
kerning first=84 second=109 amount=-3
|
<kerning first="65" second="89" amount="-9" />
|
||||||
kerning first=84 second=122 amount=-2
|
<kerning first="72" second="89" amount="-1" />
|
||||||
kerning first=89 second=99 amount=-2
|
<kerning first="119" second="44" amount="-4" />
|
||||||
kerning first=76 second=118 amount=-5
|
<kerning first="69" second="121" amount="-1" />
|
||||||
kerning first=90 second=99 amount=-1
|
<kerning first="84" second="109" amount="-3" />
|
||||||
kerning first=90 second=103 amount=-1
|
<kerning first="84" second="122" amount="-2" />
|
||||||
kerning first=79 second=89 amount=-2
|
<kerning first="89" second="99" amount="-2" />
|
||||||
kerning first=90 second=79 amount=-1
|
<kerning first="76" second="118" amount="-5" />
|
||||||
kerning first=84 second=115 amount=-4
|
<kerning first="90" second="99" amount="-1" />
|
||||||
kerning first=76 second=65 amount=1
|
<kerning first="90" second="103" amount="-1" />
|
||||||
kerning first=90 second=100 amount=-1
|
<kerning first="79" second="89" amount="-2" />
|
||||||
kerning first=118 second=46 amount=-4
|
<kerning first="90" second="79" amount="-1" />
|
||||||
kerning first=87 second=117 amount=-1
|
<kerning first="84" second="115" amount="-4" />
|
||||||
kerning first=118 second=34 amount=1
|
<kerning first="76" second="65" amount="1" />
|
||||||
kerning first=69 second=103 amount=-1
|
<kerning first="90" second="100" amount="-1" />
|
||||||
kerning first=97 second=121 amount=-1
|
<kerning first="118" second="46" amount="-4" />
|
||||||
kerning first=39 second=111 amount=-2
|
<kerning first="87" second="117" amount="-1" />
|
||||||
kerning first=72 second=88 amount=1
|
<kerning first="118" second="34" amount="1" />
|
||||||
kerning first=76 second=87 amount=-5
|
<kerning first="69" second="103" amount="-1" />
|
||||||
kerning first=69 second=119 amount=-1
|
<kerning first="97" second="121" amount="-1" />
|
||||||
kerning first=121 second=97 amount=-1
|
<kerning first="39" second="111" amount="-2" />
|
||||||
kerning first=75 second=45 amount=-8
|
<kerning first="72" second="88" amount="1" />
|
||||||
kerning first=65 second=86 amount=-9
|
<kerning first="76" second="87" amount="-5" />
|
||||||
kerning first=46 second=34 amount=-6
|
<kerning first="69" second="119" amount="-1" />
|
||||||
kerning first=76 second=84 amount=-10
|
<kerning first="121" second="97" amount="-1" />
|
||||||
kerning first=116 second=111 amount=-1
|
<kerning first="75" second="45" amount="-8" />
|
||||||
kerning first=87 second=113 amount=-1
|
<kerning first="65" second="86" amount="-9" />
|
||||||
kerning first=69 second=100 amount=-1
|
<kerning first="46" second="34" amount="-6" />
|
||||||
kerning first=97 second=118 amount=-1
|
<kerning first="76" second="84" amount="-10" />
|
||||||
kerning first=65 second=85 amount=-2
|
<kerning first="116" second="111" amount="-1" />
|
||||||
kerning first=90 second=71 amount=-1
|
<kerning first="87" second="113" amount="-1" />
|
||||||
kerning first=68 second=46 amount=-4
|
<kerning first="69" second="100" amount="-1" />
|
||||||
kerning first=65 second=79 amount=-3
|
<kerning first="97" second="118" amount="-1" />
|
||||||
kerning first=98 second=122 amount=-1
|
<kerning first="65" second="85" amount="-2" />
|
||||||
kerning first=86 second=41 amount=1
|
<kerning first="90" second="71" amount="-1" />
|
||||||
kerning first=84 second=118 amount=-3
|
<kerning first="68" second="46" amount="-4" />
|
||||||
kerning first=70 second=118 amount=-1
|
<kerning first="65" second="79" amount="-3" />
|
||||||
kerning first=121 second=111 amount=-1
|
<kerning first="98" second="122" amount="-1" />
|
||||||
kerning first=81 second=87 amount=-1
|
<kerning first="86" second="41" amount="1" />
|
||||||
kerning first=70 second=100 amount=-1
|
<kerning first="84" second="118" amount="-3" />
|
||||||
kerning first=102 second=93 amount=1
|
<kerning first="70" second="118" amount="-1" />
|
||||||
kerning first=114 second=101 amount=-1
|
<kerning first="121" second="111" amount="-1" />
|
||||||
kerning first=88 second=45 amount=-2
|
<kerning first="81" second="87" amount="-1" />
|
||||||
kerning first=39 second=103 amount=-2
|
<kerning first="70" second="100" amount="-1" />
|
||||||
kerning first=75 second=103 amount=-1
|
<kerning first="102" second="93" amount="1" />
|
||||||
kerning first=88 second=101 amount=-1
|
<kerning first="114" second="101" amount="-1" />
|
||||||
kerning first=89 second=103 amount=-2
|
<kerning first="88" second="45" amount="-2" />
|
||||||
kerning first=110 second=39 amount=-1
|
<kerning first="39" second="103" amount="-2" />
|
||||||
kerning first=89 second=89 amount=1
|
<kerning first="75" second="103" amount="-1" />
|
||||||
kerning first=87 second=65 amount=-2
|
<kerning first="88" second="101" amount="-1" />
|
||||||
kerning first=119 second=46 amount=-4
|
<kerning first="89" second="103" amount="-2" />
|
||||||
kerning first=34 second=34 amount=-4
|
<kerning first="110" second="39" amount="-1" />
|
||||||
kerning first=88 second=79 amount=-2
|
<kerning first="89" second="89" amount="1" />
|
||||||
kerning first=79 second=86 amount=-1
|
<kerning first="87" second="65" amount="-2" />
|
||||||
kerning first=76 second=119 amount=-3
|
<kerning first="119" second="46" amount="-4" />
|
||||||
kerning first=75 second=111 amount=-1
|
<kerning first="34" second="34" amount="-4" />
|
||||||
kerning first=65 second=116 amount=-4
|
<kerning first="88" second="79" amount="-2" />
|
||||||
kerning first=86 second=65 amount=-9
|
<kerning first="79" second="86" amount="-1" />
|
||||||
kerning first=70 second=84 amount=1
|
<kerning first="76" second="119" amount="-3" />
|
||||||
kerning first=75 second=117 amount=-1
|
<kerning first="75" second="111" amount="-1" />
|
||||||
kerning first=80 second=65 amount=-9
|
<kerning first="65" second="116" amount="-4" />
|
||||||
kerning first=34 second=112 amount=-1
|
<kerning first="86" second="65" amount="-9" />
|
||||||
kerning first=102 second=99 amount=-1
|
<kerning first="70" second="84" amount="1" />
|
||||||
kerning first=118 second=97 amount=-1
|
<kerning first="75" second="117" amount="-1" />
|
||||||
kerning first=89 second=81 amount=-1
|
<kerning first="80" second="65" amount="-9" />
|
||||||
kerning first=118 second=111 amount=-1
|
<kerning first="34" second="112" amount="-1" />
|
||||||
kerning first=102 second=101 amount=-1
|
<kerning first="102" second="99" amount="-1" />
|
||||||
kerning first=114 second=44 amount=-4
|
<kerning first="118" second="97" amount="-1" />
|
||||||
kerning first=90 second=119 amount=-1
|
<kerning first="89" second="81" amount="-1" />
|
||||||
kerning first=75 second=81 amount=-4
|
<kerning first="118" second="111" amount="-1" />
|
||||||
kerning first=88 second=121 amount=-1
|
<kerning first="102" second="101" amount="-1" />
|
||||||
kerning first=34 second=110 amount=-1
|
<kerning first="114" second="44" amount="-4" />
|
||||||
kerning first=86 second=100 amount=-2
|
<kerning first="90" second="119" amount="-1" />
|
||||||
kerning first=122 second=100 amount=-1
|
<kerning first="75" second="81" amount="-4" />
|
||||||
kerning first=89 second=67 amount=-1
|
<kerning first="88" second="121" amount="-1" />
|
||||||
kerning first=90 second=118 amount=-1
|
<kerning first="34" second="110" amount="-1" />
|
||||||
kerning first=84 second=84 amount=1
|
<kerning first="86" second="100" amount="-2" />
|
||||||
kerning first=121 second=34 amount=1
|
<kerning first="122" second="100" amount="-1" />
|
||||||
kerning first=91 second=74 amount=-1
|
<kerning first="89" second="67" amount="-1" />
|
||||||
kerning first=88 second=113 amount=-1
|
<kerning first="90" second="118" amount="-1" />
|
||||||
kerning first=77 second=88 amount=1
|
<kerning first="84" second="84" amount="1" />
|
||||||
kerning first=75 second=119 amount=-2
|
<kerning first="121" second="34" amount="1" />
|
||||||
kerning first=114 second=104 amount=-1
|
<kerning first="91" second="74" amount="-1" />
|
||||||
kerning first=68 second=88 amount=-2
|
<kerning first="88" second="113" amount="-1" />
|
||||||
kerning first=121 second=44 amount=-4
|
<kerning first="77" second="88" amount="1" />
|
||||||
kerning first=81 second=89 amount=-1
|
<kerning first="75" second="119" amount="-2" />
|
||||||
kerning first=102 second=39 amount=1
|
<kerning first="114" second="104" amount="-1" />
|
||||||
kerning first=74 second=65 amount=-2
|
<kerning first="68" second="88" amount="-2" />
|
||||||
kerning first=114 second=118 amount=1
|
<kerning first="121" second="44" amount="-4" />
|
||||||
kerning first=84 second=46 amount=-8
|
<kerning first="81" second="89" amount="-1" />
|
||||||
kerning first=111 second=34 amount=-1
|
<kerning first="102" second="39" amount="1" />
|
||||||
kerning first=88 second=71 amount=-2
|
<kerning first="74" second="65" amount="-2" />
|
||||||
kerning first=88 second=99 amount=-1
|
<kerning first="114" second="118" amount="1" />
|
||||||
kerning first=84 second=74 amount=-8
|
<kerning first="84" second="46" amount="-8" />
|
||||||
kerning first=39 second=109 amount=-1
|
<kerning first="111" second="34" amount="-1" />
|
||||||
kerning first=98 second=34 amount=-1
|
<kerning first="88" second="71" amount="-2" />
|
||||||
kerning first=86 second=114 amount=-1
|
<kerning first="88" second="99" amount="-1" />
|
||||||
kerning first=88 second=81 amount=-2
|
<kerning first="84" second="74" amount="-8" />
|
||||||
kerning first=70 second=74 amount=-11
|
<kerning first="39" second="109" amount="-1" />
|
||||||
kerning first=89 second=83 amount=-1
|
<kerning first="98" second="34" amount="-1" />
|
||||||
kerning first=87 second=41 amount=1
|
<kerning first="86" second="114" amount="-1" />
|
||||||
kerning first=89 second=97 amount=-3
|
<kerning first="88" second="81" amount="-2" />
|
||||||
kerning first=89 second=87 amount=1
|
<kerning first="70" second="74" amount="-11" />
|
||||||
kerning first=67 second=125 amount=-1
|
<kerning first="89" second="83" amount="-1" />
|
||||||
kerning first=89 second=93 amount=1
|
<kerning first="87" second="41" amount="1" />
|
||||||
kerning first=80 second=118 amount=1
|
<kerning first="89" second="97" amount="-3" />
|
||||||
kerning first=107 second=100 amount=-1
|
<kerning first="89" second="87" amount="1" />
|
||||||
kerning first=114 second=34 amount=1
|
<kerning first="67" second="125" amount="-1" />
|
||||||
kerning first=89 second=109 amount=-1
|
<kerning first="89" second="93" amount="1" />
|
||||||
kerning first=89 second=45 amount=-2
|
<kerning first="80" second="118" amount="1" />
|
||||||
kerning first=70 second=44 amount=-8
|
<kerning first="107" second="100" amount="-1" />
|
||||||
kerning first=34 second=39 amount=-4
|
<kerning first="114" second="34" amount="1" />
|
||||||
kerning first=88 second=67 amount=-2
|
<kerning first="89" second="109" amount="-1" />
|
||||||
kerning first=70 second=46 amount=-8
|
<kerning first="89" second="45" amount="-2" />
|
||||||
kerning first=102 second=41 amount=1
|
<kerning first="70" second="44" amount="-8" />
|
||||||
kerning first=89 second=117 amount=-1
|
<kerning first="34" second="39" amount="-4" />
|
||||||
kerning first=89 second=111 amount=-4
|
<kerning first="88" second="67" amount="-2" />
|
||||||
kerning first=89 second=115 amount=-4
|
<kerning first="70" second="46" amount="-8" />
|
||||||
kerning first=114 second=102 amount=1
|
<kerning first="102" second="41" amount="1" />
|
||||||
kerning first=89 second=125 amount=1
|
<kerning first="89" second="117" amount="-1" />
|
||||||
kerning first=89 second=121 amount=-1
|
<kerning first="89" second="111" amount="-4" />
|
||||||
kerning first=114 second=108 amount=-1
|
<kerning first="89" second="115" amount="-4" />
|
||||||
kerning first=47 second=47 amount=-8
|
<kerning first="114" second="102" amount="1" />
|
||||||
kerning first=65 second=63 amount=-2
|
<kerning first="89" second="125" amount="1" />
|
||||||
kerning first=75 second=67 amount=-4
|
<kerning first="89" second="121" amount="-1" />
|
||||||
kerning first=87 second=100 amount=-1
|
<kerning first="114" second="108" amount="-1" />
|
||||||
kerning first=111 second=104 amount=-1
|
<kerning first="47" second="47" amount="-8" />
|
||||||
kerning first=111 second=107 amount=-1
|
<kerning first="65" second="63" amount="-2" />
|
||||||
kerning first=75 second=109 amount=-1
|
<kerning first="75" second="67" amount="-4" />
|
||||||
kerning first=87 second=114 amount=-1
|
<kerning first="87" second="100" amount="-1" />
|
||||||
kerning first=111 second=120 amount=-1
|
<kerning first="111" second="104" amount="-1" />
|
||||||
kerning first=69 second=99 amount=-1
|
<kerning first="111" second="107" amount="-1" />
|
||||||
kerning first=65 second=84 amount=-6
|
<kerning first="75" second="109" amount="-1" />
|
||||||
kerning first=39 second=97 amount=-2
|
<kerning first="87" second="114" amount="-1" />
|
||||||
kerning first=121 second=46 amount=-4
|
<kerning first="111" second="120" amount="-1" />
|
||||||
kerning first=89 second=85 amount=-3
|
<kerning first="69" second="99" amount="-1" />
|
||||||
kerning first=75 second=79 amount=-4
|
<kerning first="65" second="84" amount="-6" />
|
||||||
kerning first=107 second=99 amount=-1
|
<kerning first="39" second="97" amount="-2" />
|
||||||
kerning first=102 second=100 amount=-1
|
<kerning first="121" second="46" amount="-4" />
|
||||||
kerning first=102 second=103 amount=-1
|
<kerning first="89" second="85" amount="-3" />
|
||||||
kerning first=75 second=110 amount=-1
|
<kerning first="75" second="79" amount="-4" />
|
||||||
kerning first=39 second=110 amount=-1
|
<kerning first="107" second="99" amount="-1" />
|
||||||
kerning first=69 second=84 amount=1
|
<kerning first="102" second="100" amount="-1" />
|
||||||
kerning first=84 second=111 amount=-3
|
<kerning first="102" second="103" amount="-1" />
|
||||||
kerning first=120 second=111 amount=-1
|
<kerning first="75" second="110" amount="-1" />
|
||||||
kerning first=84 second=114 amount=-3
|
<kerning first="39" second="110" amount="-1" />
|
||||||
kerning first=112 second=120 amount=-1
|
<kerning first="69" second="84" amount="1" />
|
||||||
kerning first=79 second=84 amount=-1
|
<kerning first="84" second="111" amount="-3" />
|
||||||
kerning first=84 second=117 amount=-3
|
<kerning first="120" second="111" amount="-1" />
|
||||||
kerning first=89 second=79 amount=-1
|
<kerning first="84" second="114" amount="-3" />
|
||||||
kerning first=75 second=113 amount=-1
|
<kerning first="112" second="120" amount="-1" />
|
||||||
kerning first=39 second=113 amount=-2
|
<kerning first="79" second="84" amount="-1" />
|
||||||
kerning first=80 second=44 amount=-11
|
<kerning first="84" second="117" amount="-3" />
|
||||||
kerning first=79 second=88 amount=-2
|
<kerning first="89" second="79" amount="-1" />
|
||||||
kerning first=98 second=39 amount=-1
|
<kerning first="75" second="113" amount="-1" />
|
||||||
kerning first=65 second=118 amount=-4
|
<kerning first="39" second="113" amount="-2" />
|
||||||
kerning first=65 second=34 amount=-4
|
<kerning first="80" second="44" amount="-11" />
|
||||||
kerning first=88 second=103 amount=-1
|
<kerning first="79" second="88" amount="-2" />
|
||||||
kerning first=77 second=89 amount=-1
|
<kerning first="98" second="39" amount="-1" />
|
||||||
kerning first=39 second=101 amount=-2
|
<kerning first="65" second="118" amount="-4" />
|
||||||
kerning first=75 second=101 amount=-1
|
<kerning first="65" second="34" amount="-4" />
|
||||||
kerning first=88 second=100 amount=-1
|
<kerning first="88" second="103" amount="-1" />
|
||||||
kerning first=78 second=65 amount=-3
|
<kerning first="77" second="89" amount="-1" />
|
||||||
kerning first=87 second=44 amount=-4
|
<kerning first="39" second="101" amount="-2" />
|
||||||
kerning first=67 second=41 amount=-1
|
<kerning first="75" second="101" amount="-1" />
|
||||||
kerning first=86 second=93 amount=1
|
<kerning first="88" second="100" amount="-1" />
|
||||||
kerning first=84 second=83 amount=-1
|
<kerning first="78" second="65" amount="-3" />
|
||||||
kerning first=102 second=113 amount=-1
|
<kerning first="87" second="44" amount="-4" />
|
||||||
kerning first=34 second=111 amount=-2
|
<kerning first="67" second="41" amount="-1" />
|
||||||
kerning first=70 second=111 amount=-1
|
<kerning first="86" second="93" amount="1" />
|
||||||
kerning first=86 second=99 amount=-2
|
<kerning first="84" second="83" amount="-1" />
|
||||||
kerning first=84 second=86 amount=1
|
<kerning first="102" second="113" amount="-1" />
|
||||||
kerning first=122 second=99 amount=-1
|
<kerning first="34" second="111" amount="-2" />
|
||||||
kerning first=84 second=89 amount=1
|
<kerning first="70" second="111" amount="-1" />
|
||||||
kerning first=70 second=114 amount=-1
|
<kerning first="86" second="99" amount="-2" />
|
||||||
kerning first=86 second=74 amount=-8
|
<kerning first="84" second="86" amount="1" />
|
||||||
kerning first=89 second=38 amount=-1
|
<kerning first="122" second="99" amount="-1" />
|
||||||
kerning first=87 second=97 amount=-1
|
<kerning first="84" second="89" amount="1" />
|
||||||
kerning first=76 second=86 amount=-9
|
<kerning first="70" second="114" amount="-1" />
|
||||||
kerning first=40 second=86 amount=1
|
<kerning first="86" second="74" amount="-8" />
|
||||||
kerning first=90 second=113 amount=-1
|
<kerning first="89" second="38" amount="-1" />
|
||||||
kerning first=39 second=39 amount=-4
|
<kerning first="87" second="97" amount="-1" />
|
||||||
kerning first=111 second=39 amount=-1
|
<kerning first="76" second="86" amount="-9" />
|
||||||
kerning first=90 second=117 amount=-1
|
<kerning first="40" second="86" amount="1" />
|
||||||
kerning first=89 second=41 amount=1
|
<kerning first="90" second="113" amount="-1" />
|
||||||
kerning first=65 second=121 amount=-4
|
<kerning first="39" second="39" amount="-4" />
|
||||||
kerning first=89 second=100 amount=-2
|
<kerning first="111" second="39" amount="-1" />
|
||||||
kerning first=89 second=42 amount=-2
|
<kerning first="90" second="117" amount="-1" />
|
||||||
kerning first=76 second=117 amount=-2
|
<kerning first="89" second="41" amount="1" />
|
||||||
kerning first=69 second=111 amount=-1
|
<kerning first="65" second="121" amount="-4" />
|
||||||
kerning first=46 second=39 amount=-6
|
<kerning first="89" second="100" amount="-2" />
|
||||||
kerning first=118 second=39 amount=1
|
<kerning first="89" second="42" amount="-2" />
|
||||||
kerning first=91 second=85 amount=-1
|
<kerning first="76" second="117" amount="-2" />
|
||||||
kerning first=80 second=90 amount=-1
|
<kerning first="69" second="111" amount="-1" />
|
||||||
kerning first=90 second=81 amount=-1
|
<kerning first="46" second="39" amount="-6" />
|
||||||
kerning first=69 second=117 amount=-1
|
<kerning first="118" second="39" amount="1" />
|
||||||
kerning first=76 second=39 amount=-5
|
<kerning first="91" second="85" amount="-1" />
|
||||||
kerning first=90 second=67 amount=-1
|
<kerning first="80" second="90" amount="-1" />
|
||||||
kerning first=87 second=103 amount=-1
|
<kerning first="90" second="81" amount="-1" />
|
||||||
kerning first=84 second=120 amount=-3
|
<kerning first="69" second="117" amount="-1" />
|
||||||
kerning first=89 second=101 amount=-2
|
<kerning first="76" second="39" amount="-5" />
|
||||||
kerning first=102 second=125 amount=1
|
<kerning first="90" second="67" amount="-1" />
|
||||||
kerning first=76 second=85 amount=-2
|
<kerning first="87" second="103" amount="-1" />
|
||||||
kerning first=79 second=65 amount=-3
|
<kerning first="84" second="120" amount="-3" />
|
||||||
kerning first=65 second=71 amount=-3
|
<kerning first="89" second="101" amount="-2" />
|
||||||
kerning first=79 second=44 amount=-4
|
<kerning first="102" second="125" amount="1" />
|
||||||
kerning first=97 second=39 amount=-2
|
<kerning first="76" second="85" amount="-2" />
|
||||||
kerning first=90 second=101 amount=-1
|
<kerning first="79" second="65" amount="-3" />
|
||||||
kerning first=65 second=87 amount=-5
|
<kerning first="65" second="71" amount="-3" />
|
||||||
kerning first=79 second=46 amount=-4
|
<kerning first="79" second="44" amount="-4" />
|
||||||
kerning first=87 second=99 amount=-1
|
<kerning first="97" second="39" amount="-2" />
|
||||||
kerning first=34 second=101 amount=-2
|
<kerning first="90" second="101" amount="-1" />
|
||||||
kerning first=40 second=89 amount=1
|
<kerning first="65" second="87" amount="-5" />
|
||||||
kerning first=76 second=89 amount=-8
|
<kerning first="79" second="46" amount="-4" />
|
||||||
kerning first=69 second=113 amount=-1
|
<kerning first="87" second="99" amount="-1" />
|
||||||
kerning first=120 second=103 amount=-1
|
<kerning first="34" second="101" amount="-2" />
|
||||||
kerning first=69 second=101 amount=-1
|
<kerning first="40" second="89" amount="1" />
|
||||||
kerning first=69 second=102 amount=-1
|
<kerning first="76" second="89" amount="-8" />
|
||||||
kerning first=104 second=39 amount=-1
|
<kerning first="69" second="113" amount="-1" />
|
||||||
kerning first=80 second=121 amount=1
|
<kerning first="120" second="103" amount="-1" />
|
||||||
kerning first=86 second=46 amount=-8
|
<kerning first="69" second="101" amount="-1" />
|
||||||
kerning first=65 second=81 amount=-3
|
<kerning first="69" second="102" amount="-1" />
|
||||||
kerning first=86 second=44 amount=-8
|
<kerning first="104" second="39" amount="-1" />
|
||||||
kerning first=120 second=99 amount=-1
|
<kerning first="80" second="121" amount="1" />
|
||||||
kerning first=98 second=120 amount=-1
|
<kerning first="86" second="46" amount="-8" />
|
||||||
kerning first=39 second=115 amount=-3
|
<kerning first="65" second="81" amount="-3" />
|
||||||
kerning first=121 second=39 amount=1
|
<kerning first="86" second="44" amount="-8" />
|
||||||
kerning first=88 second=118 amount=-1
|
<kerning first="120" second="99" amount="-1" />
|
||||||
kerning first=84 second=65 amount=-6
|
<kerning first="98" second="120" amount="-1" />
|
||||||
kerning first=65 second=39 amount=-4
|
<kerning first="39" second="115" amount="-3" />
|
||||||
kerning first=84 second=79 amount=-1
|
<kerning first="121" second="39" amount="1" />
|
||||||
kerning first=65 second=119 amount=-4
|
<kerning first="88" second="118" amount="-1" />
|
||||||
kerning first=70 second=117 amount=-1
|
<kerning first="84" second="65" amount="-6" />
|
||||||
kerning first=75 second=100 amount=-1
|
<kerning first="65" second="39" amount="-4" />
|
||||||
kerning first=86 second=111 amount=-2
|
<kerning first="84" second="79" amount="-1" />
|
||||||
kerning first=122 second=111 amount=-1
|
<kerning first="65" second="119" amount="-4" />
|
||||||
kerning first=81 second=84 amount=-2
|
<kerning first="70" second="117" amount="-1" />
|
||||||
kerning first=107 second=103 amount=-1
|
<kerning first="75" second="100" amount="-1" />
|
||||||
kerning first=118 second=44 amount=-4
|
<kerning first="86" second="111" amount="-2" />
|
||||||
kerning first=87 second=46 amount=-4
|
<kerning first="122" second="111" amount="-1" />
|
||||||
kerning first=87 second=101 amount=-1
|
<kerning first="81" second="84" amount="-2" />
|
||||||
kerning first=70 second=79 amount=-2
|
<kerning first="107" second="103" amount="-1" />
|
||||||
kerning first=87 second=74 amount=-2
|
<kerning first="118" second="44" amount="-4" />
|
||||||
kerning first=123 second=74 amount=-1
|
<kerning first="87" second="46" amount="-4" />
|
||||||
kerning first=76 second=71 amount=-2
|
<kerning first="87" second="101" amount="-1" />
|
||||||
kerning first=39 second=100 amount=-2
|
<kerning first="70" second="79" amount="-2" />
|
||||||
kerning first=80 second=88 amount=-1
|
<kerning first="87" second="74" amount="-2" />
|
||||||
kerning first=84 second=121 amount=-3
|
<kerning first="123" second="74" amount="-1" />
|
||||||
kerning first=112 second=122 amount=-1
|
<kerning first="76" second="71" amount="-2" />
|
||||||
kerning first=84 second=71 amount=-1
|
<kerning first="39" second="100" amount="-2" />
|
||||||
kerning first=89 second=86 amount=1
|
<kerning first="80" second="88" amount="-1" />
|
||||||
kerning first=84 second=113 amount=-3
|
<kerning first="84" second="121" amount="-3" />
|
||||||
kerning first=120 second=113 amount=-1
|
<kerning first="112" second="122" amount="-1" />
|
||||||
kerning first=89 second=44 amount=-7
|
<kerning first="84" second="71" amount="-1" />
|
||||||
kerning first=84 second=99 amount=-3
|
<kerning first="89" second="86" amount="1" />
|
||||||
kerning first=34 second=113 amount=-2
|
<kerning first="84" second="113" amount="-3" />
|
||||||
kerning first=80 second=46 amount=-11
|
<kerning first="120" second="113" amount="-1" />
|
||||||
kerning first=86 second=117 amount=-1
|
<kerning first="89" second="44" amount="-7" />
|
||||||
kerning first=110 second=34 amount=-1
|
<kerning first="84" second="99" amount="-3" />
|
||||||
kerning first=80 second=74 amount=-7
|
<kerning first="34" second="113" amount="-2" />
|
||||||
kerning first=120 second=101 amount=-1
|
<kerning first="80" second="46" amount="-11" />
|
||||||
kerning first=73 second=88 amount=1
|
<kerning first="86" second="117" amount="-1" />
|
||||||
kerning first=108 second=111 amount=-1
|
<kerning first="110" second="34" amount="-1" />
|
||||||
kerning first=34 second=115 amount=-3
|
<kerning first="80" second="74" amount="-7" />
|
||||||
kerning first=89 second=113 amount=-2
|
<kerning first="120" second="101" amount="-1" />
|
||||||
kerning first=82 second=86 amount=-3
|
<kerning first="73" second="88" amount="1" />
|
||||||
kerning first=114 second=39 amount=1
|
<kerning first="108" second="111" amount="-1" />
|
||||||
kerning first=34 second=109 amount=-1
|
<kerning first="34" second="115" amount="-3" />
|
||||||
kerning first=84 second=101 amount=-3
|
<kerning first="89" second="113" amount="-2" />
|
||||||
kerning first=70 second=121 amount=-1
|
<kerning first="82" second="86" amount="-3" />
|
||||||
kerning first=123 second=85 amount=-1
|
<kerning first="114" second="39" amount="1" />
|
||||||
kerning first=122 second=103 amount=-1
|
<kerning first="34" second="109" amount="-1" />
|
||||||
kerning first=86 second=97 amount=-2
|
<kerning first="84" second="101" amount="-3" />
|
||||||
kerning first=82 second=89 amount=-4
|
<kerning first="70" second="121" amount="-1" />
|
||||||
kerning first=66 second=84 amount=-1
|
<kerning first="123" second="85" amount="-1" />
|
||||||
kerning first=84 second=97 amount=-4
|
<kerning first="122" second="103" amount="-1" />
|
||||||
kerning first=86 second=103 amount=-2
|
<kerning first="86" second="97" amount="-2" />
|
||||||
kerning first=70 second=113 amount=-1
|
<kerning first="82" second="89" amount="-4" />
|
||||||
kerning first=84 second=87 amount=1
|
<kerning first="66" second="84" amount="-1" />
|
||||||
kerning first=75 second=112 amount=-1
|
<kerning first="84" second="97" amount="-4" />
|
||||||
kerning first=114 second=111 amount=-1
|
<kerning first="86" second="103" amount="-2" />
|
||||||
kerning first=39 second=112 amount=-1
|
<kerning first="70" second="113" amount="-1" />
|
||||||
kerning first=107 second=101 amount=-1
|
<kerning first="84" second="87" amount="1" />
|
||||||
kerning first=82 second=84 amount=-3
|
<kerning first="75" second="112" amount="-1" />
|
||||||
kerning first=114 second=121 amount=1
|
<kerning first="114" second="111" amount="-1" />
|
||||||
kerning first=34 second=99 amount=-2
|
<kerning first="39" second="112" amount="-1" />
|
||||||
kerning first=70 second=81 amount=-2
|
<kerning first="107" second="101" amount="-1" />
|
||||||
kerning first=111 second=122 amount=-1
|
<kerning first="82" second="84" amount="-3" />
|
||||||
kerning first=84 second=67 amount=-1
|
<kerning first="114" second="121" amount="1" />
|
||||||
kerning first=111 second=108 amount=-1
|
<kerning first="34" second="99" amount="-2" />
|
||||||
kerning first=89 second=84 amount=1
|
<kerning first="70" second="81" amount="-2" />
|
||||||
kerning first=76 second=79 amount=-2
|
<kerning first="111" second="122" amount="-1" />
|
||||||
kerning first=85 second=65 amount=-2
|
<kerning first="84" second="67" amount="-1" />
|
||||||
kerning first=44 second=34 amount=-6
|
<kerning first="111" second="108" amount="-1" />
|
||||||
kerning first=65 second=67 amount=-3
|
<kerning first="89" second="84" amount="1" />
|
||||||
kerning first=109 second=34 amount=-1
|
<kerning first="76" second="79" amount="-2" />
|
||||||
kerning first=114 second=103 amount=-1
|
<kerning first="85" second="65" amount="-2" />
|
||||||
kerning first=78 second=89 amount=-1
|
<kerning first="44" second="34" amount="-6" />
|
||||||
kerning first=89 second=114 amount=-1
|
<kerning first="65" second="67" amount="-3" />
|
||||||
kerning first=89 second=112 amount=-1
|
<kerning first="109" second="34" amount="-1" />
|
||||||
kerning first=34 second=65 amount=-4
|
<kerning first="114" second="103" amount="-1" />
|
||||||
kerning first=70 second=65 amount=-11
|
<kerning first="78" second="89" amount="-1" />
|
||||||
kerning first=81 second=86 amount=-1
|
<kerning first="89" second="114" amount="-1" />
|
||||||
kerning first=114 second=119 amount=1
|
<kerning first="89" second="112" amount="-1" />
|
||||||
kerning first=89 second=102 amount=-1
|
<kerning first="34" second="65" amount="-4" />
|
||||||
kerning first=84 second=45 amount=-8
|
<kerning first="70" second="65" amount="-11" />
|
||||||
kerning first=86 second=125 amount=1
|
<kerning first="81" second="86" amount="-1" />
|
||||||
kerning first=70 second=67 amount=-2
|
<kerning first="114" second="119" amount="1" />
|
||||||
kerning first=89 second=116 amount=-1
|
<kerning first="89" second="102" amount="-1" />
|
||||||
kerning first=102 second=34 amount=1
|
<kerning first="84" second="45" amount="-8" />
|
||||||
kerning first=114 second=99 amount=-1
|
<kerning first="86" second="125" amount="1" />
|
||||||
kerning first=67 second=84 amount=-1
|
<kerning first="70" second="67" amount="-2" />
|
||||||
kerning first=114 second=113 amount=-1
|
<kerning first="89" second="116" amount="-1" />
|
||||||
kerning first=89 second=122 amount=-1
|
<kerning first="102" second="34" amount="1" />
|
||||||
kerning first=89 second=118 amount=-1
|
<kerning first="114" second="99" amount="-1" />
|
||||||
kerning first=70 second=71 amount=-2
|
<kerning first="67" second="84" amount="-1" />
|
||||||
kerning first=114 second=107 amount=-1
|
<kerning first="114" second="113" amount="-1" />
|
||||||
kerning first=89 second=120 amount=-1
|
<kerning first="89" second="122" amount="-1" />
|
||||||
|
<kerning first="89" second="118" amount="-1" />
|
||||||
|
<kerning first="70" second="71" amount="-2" />
|
||||||
|
<kerning first="114" second="107" amount="-1" />
|
||||||
|
<kerning first="89" second="120" amount="-1" />
|
||||||
|
</kernings>
|
||||||
|
</font>
|
||||||
|
|||||||
@ -26,6 +26,36 @@
|
|||||||
color: var(--banner-url-colour);
|
color: var(--banner-url-colour);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#options:focus {
|
||||||
|
background-color: #eef3ec;
|
||||||
|
border: solid black 2px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#support:focus {
|
||||||
|
background-color: #eef3ec;
|
||||||
|
border: solid black 2px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#notice:focus {
|
||||||
|
background-color: #eef3ec;
|
||||||
|
border: solid black 2px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#banner .col a:focus {
|
||||||
|
background-color: #eef3ec;
|
||||||
|
border: solid black 2px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#notice-wrapper #notice:focus {
|
||||||
|
background-color: #eef3ec;
|
||||||
|
border: solid black 2px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
#notice-wrapper {
|
#notice-wrapper {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@ -78,6 +78,11 @@
|
|||||||
border-left: 2px solid var(--primary-border-colour);
|
border-left: 2px solid var(--primary-border-colour);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p a:focus {
|
||||||
|
color: #0a6ebd;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
.checkbox label input[type=checkbox]+.checkbox-decorator .check,
|
.checkbox label input[type=checkbox]+.checkbox-decorator .check,
|
||||||
.checkbox label input[type=checkbox]+.checkbox-decorator .check::before {
|
.checkbox label input[type=checkbox]+.checkbox-decorator .check::before {
|
||||||
border-color: var(--input-border-colour);
|
border-color: var(--input-border-colour);
|
||||||
|
|||||||
@ -249,6 +249,13 @@ optgroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Bootstrap form inside CodeMirror editor */
|
||||||
|
|
||||||
|
.cm-panel > .bmd-form-group {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* CodeMirror */
|
/* CodeMirror */
|
||||||
|
|
||||||
.ͼ2 .cm-specialChar,
|
.ͼ2 .cm-specialChar,
|
||||||
|
|||||||
@ -57,6 +57,18 @@ class ControlsWaiter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks or unchecks the Auto Bake checkbox with "Enter"
|
||||||
|
* @param {Event} ev
|
||||||
|
*/
|
||||||
|
autoBakeKeyboardHandler(ev) {
|
||||||
|
const checkBox = document.getElementById("auto-bake");
|
||||||
|
ev.preventDefault();
|
||||||
|
if (ev.key === "Enter" || ev.key === " ") {
|
||||||
|
checkBox.checked = !checkBox.checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler to trigger baking.
|
* Handler to trigger baking.
|
||||||
@ -387,6 +399,18 @@ class ControlsWaiter {
|
|||||||
*/
|
*/
|
||||||
supportButtonClick(e) {
|
supportButtonClick(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
const faqs = document.getElementById("faqs");
|
||||||
|
const faqsAElement = faqs.getElementsByTagName("a");
|
||||||
|
for (let i = 0; i < faqsAElement.length; i++) {
|
||||||
|
faqsAElement[i].setAttribute("tabindex", "0");
|
||||||
|
faqsAElement[i].addEventListener("keydown", this.navigateFAQList, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabs = document.querySelectorAll('[role="tab"]');
|
||||||
|
|
||||||
|
for (let i = 0; i < tabs.length; i++) {
|
||||||
|
tabs[i].addEventListener("keydown", this.changeTabs, false);
|
||||||
|
}
|
||||||
|
|
||||||
const reportBugInfo = document.getElementById("report-bug-info");
|
const reportBugInfo = document.getElementById("report-bug-info");
|
||||||
const saveLink = this.generateStateUrl(true, true, null, null, "https://gchq.github.io/CyberChef/");
|
const saveLink = this.generateStateUrl(true, true, null, null, "https://gchq.github.io/CyberChef/");
|
||||||
@ -403,6 +427,64 @@ ${navigator.userAgent}
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Event} ev
|
||||||
|
*/
|
||||||
|
changeTabs(ev) {
|
||||||
|
const tab = ev.target;
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
if (ev.key === "ArrowRight") {
|
||||||
|
const nextTab = tab.parentElement;
|
||||||
|
if (nextTab.nextElementSibling === null) {
|
||||||
|
tab.parentElement.parentElement.firstElementChild.firstElementChild.focus();
|
||||||
|
} else {
|
||||||
|
nextTab.nextElementSibling.firstElementChild.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (ev.key === "ArrowLeft") {
|
||||||
|
const prevTab = tab.parentElement;
|
||||||
|
|
||||||
|
if (prevTab.previousElementSibling === null) {
|
||||||
|
tab.parentElement.parentElement.lastElementChild.firstElementChild.focus();
|
||||||
|
} else {
|
||||||
|
prevTab.previousElementSibling.firstElementChild.focus();
|
||||||
|
}
|
||||||
|
} else if (ev.key === "Tab" && !ev.shiftKey && ev.target === document.getElementById("tab-1")) {
|
||||||
|
document.getElementById("faqs").querySelector("[class='btn btn-primary']").focus();
|
||||||
|
} else if (ev.key === "Tab" && !ev.shiftKey && ev.target === document.getElementById("tab-2")) {
|
||||||
|
document.getElementById("report-bug").querySelector("[class='btn btn-primary']").focus();
|
||||||
|
} else if (ev.key === "Tab" && !ev.shiftKey && ev.target === document.getElementById("tab-3")) {
|
||||||
|
document.getElementById("about").querySelector("[href]").focus();
|
||||||
|
} else if (ev.key === "Tab" && !ev.shiftKey && ev.target === document.getElementById("tab-4")) {
|
||||||
|
const button = document.getElementById("support-modal").getElementsByClassName("modal-footer");
|
||||||
|
const close = button[0].firstElementChild;
|
||||||
|
close.focus();
|
||||||
|
} else if (ev.key === "Enter" || ev.key === "Space" || ev.key === " ") {
|
||||||
|
tab.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Event} ev
|
||||||
|
*/
|
||||||
|
navigateFAQList(ev) {
|
||||||
|
|
||||||
|
const el = ev.target.nextElementSibling;
|
||||||
|
if (ev.key === "Enter" || ev.key === "Space" || ev.key === " ") {
|
||||||
|
ev.preventDefault();
|
||||||
|
const question = el.classList;
|
||||||
|
if (question !== undefined && question.value) {
|
||||||
|
if (!question.value.includes("show")) {
|
||||||
|
question.add("show");
|
||||||
|
} else if (question.contains("show")) {
|
||||||
|
question.remove("show");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the stale indicator to show that the input or recipe has changed
|
* Shows the stale indicator to show that the input or recipe has changed
|
||||||
* since the last bake.
|
* since the last bake.
|
||||||
|
|||||||
@ -151,8 +151,26 @@ class InputWaiter {
|
|||||||
// Event handlers
|
// Event handlers
|
||||||
EditorView.domEventHandlers({
|
EditorView.domEventHandlers({
|
||||||
paste(event, view) {
|
paste(event, view) {
|
||||||
|
const clipboardData = event.clipboardData;
|
||||||
|
const items = clipboardData.items;
|
||||||
|
let files = [];
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
const item = items[i];
|
||||||
|
if (item.kind === "string") {
|
||||||
|
// If there are any string items they should be preferred over
|
||||||
|
// files.
|
||||||
|
files = [];
|
||||||
|
break;
|
||||||
|
} else if (item.kind === "file") {
|
||||||
|
files.push(item.getAsFile());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (files.length > 0) {
|
||||||
|
// Prevent the default paste behavior, afterPaste will load the files instead
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
self.afterPaste(event);
|
self.afterPaste(files);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -914,9 +932,12 @@ class InputWaiter {
|
|||||||
* Handler that fires just after input paste events.
|
* Handler that fires just after input paste events.
|
||||||
* Checks whether the EOL separator or character encoding should be updated.
|
* Checks whether the EOL separator or character encoding should be updated.
|
||||||
*
|
*
|
||||||
* @param {event} e
|
* @param {File[]} files - An array of any files that were included in the paste event
|
||||||
*/
|
*/
|
||||||
afterPaste(e) {
|
afterPaste(files) {
|
||||||
|
if (files.length > 0) {
|
||||||
|
this.loadUIFiles(files);
|
||||||
|
}
|
||||||
// If EOL has been fixed, skip this.
|
// If EOL has been fixed, skip this.
|
||||||
if (this.eolState > 1) return;
|
if (this.eolState > 1) return;
|
||||||
|
|
||||||
|
|||||||
@ -28,17 +28,16 @@ class OperationsWaiter {
|
|||||||
this.removeIntent = false;
|
this.removeIntent = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for search events.
|
* Handler for search events.
|
||||||
* Finds operations which match the given search term and displays them under the search box.
|
* Finds operations which match the given search term and displays them under the search box.
|
||||||
*
|
*
|
||||||
* @param {event} e
|
* @param {KeyboardEvent | ClipboardEvent | Event} e
|
||||||
*/
|
*/
|
||||||
searchOperations(e) {
|
searchOperations(e) {
|
||||||
let ops, selected;
|
let ops, selected;
|
||||||
|
|
||||||
if (e.type === "search" || e.keyCode === 13) { // Search or Return
|
if ((e.type === "search" && e.target.value !== "") || e.keyCode === 13) { // Search (non-empty) or Return
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
ops = document.querySelectorAll("#search-results li");
|
ops = document.querySelectorAll("#search-results li");
|
||||||
if (ops.length) {
|
if (ops.length) {
|
||||||
@ -49,27 +48,43 @@ class OperationsWaiter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the operation element with the correct attributes when selected
|
||||||
|
* @param {HTMLElement} element
|
||||||
|
*/
|
||||||
|
const _selectOperation = (element) => {
|
||||||
|
element.classList.add("selected-op");
|
||||||
|
element.scrollIntoView({block: "nearest"});
|
||||||
|
$(element).popover("show");
|
||||||
|
e.target.setAttribute("aria-activedescendant", element.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the operation element with the correct attributes when deselected
|
||||||
|
* @param {HTMLElement} element
|
||||||
|
*/
|
||||||
|
const _deselectOperation = (element) => {
|
||||||
|
element.classList.remove("selected-op");
|
||||||
|
$(element).popover("hide");
|
||||||
|
};
|
||||||
|
|
||||||
if (e.keyCode === 40) { // Down
|
if (e.keyCode === 40) { // Down
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
ops = document.querySelectorAll("#search-results li");
|
ops = document.querySelectorAll("#search-results li");
|
||||||
if (ops.length) {
|
if (ops.length) {
|
||||||
selected = this.getSelectedOp(ops);
|
selected = this.getSelectedOp(ops);
|
||||||
if (selected > -1) {
|
if (selected > -1) _deselectOperation(ops[selected]);
|
||||||
ops[selected].classList.remove("selected-op");
|
|
||||||
}
|
|
||||||
if (selected === ops.length-1) selected = -1;
|
if (selected === ops.length-1) selected = -1;
|
||||||
ops[selected+1].classList.add("selected-op");
|
_selectOperation(ops[selected+1]);
|
||||||
}
|
}
|
||||||
} else if (e.keyCode === 38) { // Up
|
} else if (e.keyCode === 38) { // Up
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
ops = document.querySelectorAll("#search-results li");
|
ops = document.querySelectorAll("#search-results li");
|
||||||
if (ops.length) {
|
if (ops.length) {
|
||||||
selected = this.getSelectedOp(ops);
|
selected = this.getSelectedOp(ops);
|
||||||
if (selected > -1) {
|
if (selected > -1) _deselectOperation(ops[selected]);
|
||||||
ops[selected].classList.remove("selected-op");
|
|
||||||
}
|
|
||||||
if (selected === 0) selected = ops.length;
|
if (selected === 0) selected = ops.length;
|
||||||
ops[selected-1].classList.add("selected-op");
|
_selectOperation(ops[selected-1]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const searchResultsEl = document.getElementById("search-results");
|
const searchResultsEl = document.getElementById("search-results");
|
||||||
@ -83,11 +98,13 @@ class OperationsWaiter {
|
|||||||
searchResultsEl.removeChild(searchResultsEl.firstChild);
|
searchResultsEl.removeChild(searchResultsEl.firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.querySelector("#search").removeAttribute("aria-activedescendant");
|
||||||
|
|
||||||
$("#categories .show").collapse("hide");
|
$("#categories .show").collapse("hide");
|
||||||
if (str) {
|
if (str) {
|
||||||
const matchedOps = this.filterOperations(str, true);
|
const matchedOps = this.filterOperations(str, true);
|
||||||
const matchedOpsHtml = matchedOps
|
const matchedOpsHtml = matchedOps
|
||||||
.map(v => v.toStubHtml())
|
.map((operation, idx) => operation.toStubHtml(false, `search-result-${idx}`))
|
||||||
.join("");
|
.join("");
|
||||||
|
|
||||||
searchResultsEl.innerHTML = matchedOpsHtml;
|
searchResultsEl.innerHTML = matchedOpsHtml;
|
||||||
@ -103,7 +120,7 @@ class OperationsWaiter {
|
|||||||
* @param {string} searchStr
|
* @param {string} searchStr
|
||||||
* @param {boolean} highlight - Whether or not to highlight the matching string in the operation
|
* @param {boolean} highlight - Whether or not to highlight the matching string in the operation
|
||||||
* name and description
|
* name and description
|
||||||
* @returns {string[]}
|
* @returns {HTMLOperation[]}
|
||||||
*/
|
*/
|
||||||
filterOperations(inStr, highlight) {
|
filterOperations(inStr, highlight) {
|
||||||
const matchedOps = [];
|
const matchedOps = [];
|
||||||
|
|||||||
@ -417,6 +417,8 @@ class RecipeWaiter {
|
|||||||
el.classList.add("flow-control-op");
|
el.classList.add("flow-control-op");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$(el).find("[data-toggle='tooltip']").tooltip();
|
||||||
|
|
||||||
// Disable auto-bake if this is a manual op
|
// Disable auto-bake if this is a manual op
|
||||||
if (op.manualBake && this.app.autoBake_) {
|
if (op.manualBake && this.app.autoBake_) {
|
||||||
this.manager.controls.setAutoBake(false);
|
this.manager.controls.setAutoBake(false);
|
||||||
@ -442,8 +444,6 @@ class RecipeWaiter {
|
|||||||
this.buildRecipeOperation(item);
|
this.buildRecipeOperation(item);
|
||||||
document.getElementById("rec-list").appendChild(item);
|
document.getElementById("rec-list").appendChild(item);
|
||||||
|
|
||||||
$(item).find("[data-toggle='tooltip']").tooltip();
|
|
||||||
|
|
||||||
item.dispatchEvent(this.manager.operationadd);
|
item.dispatchEvent(this.manager.operationadd);
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Dish from "../../core/Dish.mjs";
|
import Dish from "../../core/Dish.mjs";
|
||||||
|
import DishError from "../../core/errors/DishError.mjs";
|
||||||
|
import { CHR_ENC_SIMPLE_REVERSE_LOOKUP } from "../../core/lib/ChrEnc.mjs";
|
||||||
import Utils from "../../core/Utils.mjs";
|
import Utils from "../../core/Utils.mjs";
|
||||||
import cptable from "codepage";
|
import cptable from "codepage";
|
||||||
import loglevelMessagePrefix from "loglevel-message-prefix";
|
import loglevelMessagePrefix from "loglevel-message-prefix";
|
||||||
@ -98,7 +100,7 @@ async function bufferToStr(data) {
|
|||||||
try {
|
try {
|
||||||
str = cptable.utils.decode(data.encoding, new Uint8Array(data.buffer));
|
str = cptable.utils.decode(data.encoding, new Uint8Array(data.buffer));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
str = err;
|
str = new DishError(`Error decoding buffer with encoding ${CHR_ENC_SIMPLE_REVERSE_LOOKUP[data.encoding]}: ${err.message}`).toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,7 +34,14 @@ module.exports = {
|
|||||||
testOp(browser, "AES Encrypt", "test input", "e42eb8fbfb7a98fff061cd2c1a794d92", [{"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, {"option": "Hex", "string": "00000000000000000000000000000000"}, "CBC", "Raw", "Hex"]);
|
testOp(browser, "AES Encrypt", "test input", "e42eb8fbfb7a98fff061cd2c1a794d92", [{"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, {"option": "Hex", "string": "00000000000000000000000000000000"}, "CBC", "Raw", "Hex"]);
|
||||||
testOp(browser, "AND", "test input", "4$04 $044", [{ "option": "Hex", "string": "34" }]);
|
testOp(browser, "AND", "test input", "4$04 $044", [{ "option": "Hex", "string": "34" }]);
|
||||||
testOp(browser, "Add line numbers", "test input", "1 test input");
|
testOp(browser, "Add line numbers", "test input", "1 test input");
|
||||||
testOp(browser, ["From Hex", "Add Text To Image", "To Base64"], Images.PNG_HEX, Images.PNG_CHEF_B64, [[], ["Chef", "Center", "Middle", 0, 0, 16], []]);
|
testOp(browser, ["From Hex", "Add Text To Image", "SHA2"], Images.PNG_HEX, "50cdf8ea483c55564a091650c2bccb4586f919b721e5fe9d6a61660505b4346d6ebdb2ef0cf075a7728cd84cb26ea3e477b5bd86a94a49a27d79423994afb60a", [[], ["Chef", "Center", "Middle", 0, 0, 16, "Roboto"], []]);
|
||||||
|
testOp(browser, ["From Hex", "Add Text To Image", "SHA2"], Images.PNG_HEX, "78b3055463d9167dd039e47f451acaf06c593d209f8e405b4e18011cdcf190dc0af5952be887d93c0ebd38738e978120c1294c71104e6b00d3f9de8d6320ec1c", [[], ["Chef", "Center", "Middle", 0, 0, 16, "Roboto Black"], []]);
|
||||||
|
testOp(browser, ["From Hex", "Add Text To Image", "SHA2"], Images.PNG_HEX, "4ab4d4b6cb22ad700f6cd144c2c8ecad2a094f21a1d1d5d48eb6c8f97417192f89b4512f6a78276d49668ebef5e89c3a4d14860cb79399a0dafce98c92209e07", [[], ["Chef", "Center", "Middle", 0, 0, 16, "Roboto Mono"], []]);
|
||||||
|
testOp(browser, ["From Hex", "Add Text To Image", "SHA2"], Images.PNG_HEX, "11490db4907516b4d9e256da1ac0b02b561fa7547971e6316a8a0b90c9c66585a11f3145672c6d972b1a221d3bfad9c8a97de7ff77fd9442ebc40f39c1ef9ef7", [[], ["Chef", "Center", "Middle", 0, 0, 16, "Roboto Slab"], []]);
|
||||||
|
testOp(browser, ["From Hex", "Dither Image", "SHA2"], Images.PNG_HEX, "cbf587a78915cfb14546ba83080b13e5054800802488dd0cb786b8951e7dc0b48f055260917bd0ccfc075e422b9d6aff112948562653995d74e70f0b66367ac3", [[], [], []]);
|
||||||
|
testOp(browser, ["From Hex", "Generate Image", "SHA2"], Images.PNG_HEX, "2c451762a6c9192fd31dc80765eab3f447be70ea51f6fdb6911ade4d89d4a98bd0a1ff00b08d76aac472faeceb54b66092e3f3be7bbf899bf3e55ca9c96a56aa", [[], [], []]);
|
||||||
|
testOp(browser, ["From Hex", "Image Hue/Saturation/Lightness", "SHA2"], Images.PNG_HEX, "522dfc0bbef00e05c5d6861a002039fa2952e4bbb7fe8d21d0d538ef6f9d65da82065929b4150dc5b8b49460ee6c9bef7f660b86f8d4e7442a07c61c0a152a4b", [[], [50, 50, 50], []]);
|
||||||
|
testOp(browser, ["From Hex", "Resize Image", "SHA2"], Images.PNG_HEX, "654bfbf0a0537c901459c4bc22c5fb0bacbf01af775a0733e3a1c46cda5b699bcc4ed85322d813c7bb9b245d62d64425c0766fe03d3d20bc63634e2a4df17626", [[], [64, 64], []]);
|
||||||
testOp(browser, "Adler-32 Checksum", "test input", "16160411");
|
testOp(browser, "Adler-32 Checksum", "test input", "16160411");
|
||||||
testOp(browser, "Affine Cipher Decode", "test input", "rcqr glnsr", [1, 2]);
|
testOp(browser, "Affine Cipher Decode", "test input", "rcqr glnsr", [1, 2]);
|
||||||
testOp(browser, "Affine Cipher Encode", "test input", "gndg zoujg", [3, 1]);
|
testOp(browser, "Affine Cipher Encode", "test input", "gndg zoujg", [3, 1]);
|
||||||
@ -43,6 +50,14 @@ module.exports = {
|
|||||||
testOp(browser, "Analyse hash", "0123456789abcdef", /CRC-64/);
|
testOp(browser, "Analyse hash", "0123456789abcdef", /CRC-64/);
|
||||||
testOp(browser, "Atbash Cipher", "test input", "gvhg rmkfg");
|
testOp(browser, "Atbash Cipher", "test input", "gvhg rmkfg");
|
||||||
// testOp(browser, "Avro to JSON", "test input", "test_output");
|
// testOp(browser, "Avro to JSON", "test input", "test_output");
|
||||||
|
testOp(browser,
|
||||||
|
[
|
||||||
|
"From Hex", "Avro to JSON"
|
||||||
|
],
|
||||||
|
"4f626a0104166176726f2e736368656d6196017b2274797065223a227265636f7264222c226e616d65223a22736d616c6c222c226669656c6473223a5b7b226e616d65223a226e616d65222c2274797065223a22737472696e67227d5d7d146176726f2e636f646563086e756c6c004e0247632e3702e5b75cdab9a62f1541020e0c6d796e616d654e0247632e3702e5b75cdab9a62f1541",
|
||||||
|
'{"name":"myname"}\n',
|
||||||
|
[[], [false]]
|
||||||
|
);
|
||||||
testOp(browser, "BLAKE2b", "test input", "33ebdc8f38177f3f3f334eeb117a84e11f061bbca4db6b8923e5cec85103f59f415551a5d5a933fdb6305dc7bf84671c2540b463dbfa08ee1895cfaa5bd780b5", ["512", "Hex", { "option": "UTF8", "string": "pass" }]);
|
testOp(browser, "BLAKE2b", "test input", "33ebdc8f38177f3f3f334eeb117a84e11f061bbca4db6b8923e5cec85103f59f415551a5d5a933fdb6305dc7bf84671c2540b463dbfa08ee1895cfaa5bd780b5", ["512", "Hex", { "option": "UTF8", "string": "pass" }]);
|
||||||
testOp(browser, "BLAKE2s", "test input", "defe73d61dfa6e5807e4f9643e159a09ccda6be3c26dcd65f8a9bb38bfc973a7", ["256", "Hex", { "option": "UTF8", "string": "pass" }]);
|
testOp(browser, "BLAKE2s", "test input", "defe73d61dfa6e5807e4f9643e159a09ccda6be3c26dcd65f8a9bb38bfc973a7", ["256", "Hex", { "option": "UTF8", "string": "pass" }]);
|
||||||
testOp(browser, "BSON deserialise", "\u0011\u0000\u0000\u0000\u0002a\u0000\u0005\u0000\u0000\u0000test\u0000\u0000", '{\u000A "a": "test"\u000A}');
|
testOp(browser, "BSON deserialise", "\u0011\u0000\u0000\u0000\u0002a\u0000\u0005\u0000\u0000\u0000test\u0000\u0000", '{\u000A "a": "test"\u000A}');
|
||||||
@ -58,7 +73,10 @@ module.exports = {
|
|||||||
testOp(browser, "Bit shift right", "test input", ":29:\u0010478::");
|
testOp(browser, "Bit shift right", "test input", ":29:\u0010478::");
|
||||||
testOp(browser, "Blowfish Decrypt", "10884e15427dd84ec35204e9c8e921ae", "test_output", [{"option": "Hex", "string": "1234567801234567"}, {"option": "Hex", "string": "0011223344556677"}, "CBC", "Hex", "Raw"]);
|
testOp(browser, "Blowfish Decrypt", "10884e15427dd84ec35204e9c8e921ae", "test_output", [{"option": "Hex", "string": "1234567801234567"}, {"option": "Hex", "string": "0011223344556677"}, "CBC", "Hex", "Raw"]);
|
||||||
testOp(browser, "Blowfish Encrypt", "test input", "f0fadbd1d90d774f714248cf26b96410", [{"option": "Hex", "string": "1234567801234567"}, {"option": "Hex", "string": "0011223344556677"}, "CBC", "Raw", "Hex"]);
|
testOp(browser, "Blowfish Encrypt", "test input", "f0fadbd1d90d774f714248cf26b96410", [{"option": "Hex", "string": "1234567801234567"}, {"option": "Hex", "string": "0011223344556677"}, "CBC", "Raw", "Hex"]);
|
||||||
testOp(browser, ["From Hex", "Blur Image", "To Base64"], Images.PNG_HEX, Images.PNG_BLUR_B64);
|
testOp(browser, ["From Hex", "Blur Image", "SHA2"], Images.PNG_HEX, "24f2e89f3e00cc35f551bbc48ea82e76474946ce0282183494d1ca3d3b0012c27b6102c4368ae056dc7fecb6df7886d86ff3d29b7e5965493f30c371eee9a24e");
|
||||||
|
testOp(browser, ["From Hex", "Blur Image", "SHA2"], Images.PNG_HEX, "2c49d89fc10c94352c9a19f82de353c37928831d6f976a6b36eb918825a0ba027980801838228a4a0da63f1886e4fa59b6666f992ad2d2b7d4622253dc034052", [[], [5, "Gaussian"], []]);
|
||||||
|
testOp(browser, ["From Hex", "Sharpen Image", "SHA2"], Images.PNG_HEX, "acc7027642c2eeb67d7356a80ed8a1bdce9adabf656ea1294e47723f506626a7aa41f1660fa844a1e1e83b17180017ab0d5bccd7f6a341692832020dc887eaa5");
|
||||||
|
testOp(browser, ["From Hex", "Contain Image", "SHA2"], Images.PNG_HEX, "cb871ad0722d487d56a2b18247b1aa30ecc244eb717e08e23a55cae78759553312dc1717196d7cb9daa04743e57c56fc3901ba92be5a68fb03c377f718e8efe7");
|
||||||
testOpHtml(browser, "Bombe", "XTSYN WAEUG EZALY NRQIM AMLZX MFUOD AWXLY LZCUZ QOQBQ JLCPK NDDRW F", "table tr:last-child td:first-child", "ECG", ["3-rotor", "LEYJVCNIXWPBQMDRTAKZGFUHOS", "BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "ESOVPZJAYQUIRHXLNFTGKDCMWB<K", "AY BR CU DH EQ FS GL IP JX KN MO TZ VW", "HELLO CYBER CHEFU SER", 0, true]);
|
testOpHtml(browser, "Bombe", "XTSYN WAEUG EZALY NRQIM AMLZX MFUOD AWXLY LZCUZ QOQBQ JLCPK NDDRW F", "table tr:last-child td:first-child", "ECG", ["3-rotor", "LEYJVCNIXWPBQMDRTAKZGFUHOS", "BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "ESOVPZJAYQUIRHXLNFTGKDCMWB<K", "AY BR CU DH EQ FS GL IP JX KN MO TZ VW", "HELLO CYBER CHEFU SER", 0, true]);
|
||||||
testOp(browser, ["Bzip2 Compress", "To Hex"], "test input", "42 5a 68 39 31 41 59 26 53 59 cf 96 82 1d 00 00 03 91 80 40 00 02 21 4e 00 20 00 21 90 c2 10 c0 88 33 92 8e df 17 72 45 38 50 90 cf 96 82 1d");
|
testOp(browser, ["Bzip2 Compress", "To Hex"], "test input", "42 5a 68 39 31 41 59 26 53 59 cf 96 82 1d 00 00 03 91 80 40 00 02 21 4e 00 20 00 21 90 c2 10 c0 88 33 92 8e df 17 72 45 38 50 90 cf 96 82 1d");
|
||||||
testOp(browser, ["From Hex", "Bzip2 Decompress"], "425a68393141592653597b0884b7000003038000008200ce00200021a647a4218013709517c5dc914e14241ec2212dc0", "test_output", [[], [true]]);
|
testOp(browser, ["From Hex", "Bzip2 Decompress"], "425a68393141592653597b0884b7000003038000008200ce00200021a647a4218013709517c5dc914e14241ec2212dc0", "test_output", [[], [true]]);
|
||||||
@ -196,6 +214,7 @@ module.exports = {
|
|||||||
testOpHtml(browser, "Index of Coincidence", "test input", "", /Index of Coincidence: 0.08333333333333333/);
|
testOpHtml(browser, "Index of Coincidence", "test input", "", /Index of Coincidence: 0.08333333333333333/);
|
||||||
testOpImage(browser, "Invert Image", "files/Hitchhikers_Guide.jpeg");
|
testOpImage(browser, "Invert Image", "files/Hitchhikers_Guide.jpeg");
|
||||||
// testOp(browser, "JPath expression", "test input", "test_output");
|
// testOp(browser, "JPath expression", "test input", "test_output");
|
||||||
|
testOp(browser, "Jq", '{"a":{"b":1}}', '{"b":1}', [".a"]);
|
||||||
testOpHtml(browser, "JSON Beautify", "{a:1}", ".json-dict .json-literal", "1");
|
testOpHtml(browser, "JSON Beautify", "{a:1}", ".json-dict .json-literal", "1");
|
||||||
// testOp(browser, "JSON Minify", "test input", "test_output");
|
// testOp(browser, "JSON Minify", "test input", "test_output");
|
||||||
// testOp(browser, "JSON to CSV", "test input", "test_output");
|
// testOp(browser, "JSON to CSV", "test input", "test_output");
|
||||||
@ -249,7 +268,7 @@ module.exports = {
|
|||||||
testOpHtml(browser, "Parse colour code", "#000", ".colorpicker-preview", "rgb(0, 0, 0)");
|
testOpHtml(browser, "Parse colour code", "#000", ".colorpicker-preview", "rgb(0, 0, 0)");
|
||||||
testOpHtml(browser, "Parse DateTime", "01/12/2000 13:00:00", "", /Date: Friday 1st December 2000/);
|
testOpHtml(browser, "Parse DateTime", "01/12/2000 13:00:00", "", /Date: Friday 1st December 2000/);
|
||||||
// testOp(browser, "Parse IP range", "test input", "test_output");
|
// testOp(browser, "Parse IP range", "test input", "test_output");
|
||||||
testOpHtml(browser, "Parse IPv4 header", "45 c0 00 c4 02 89 00 00 ff 11 1e 8c c0 a8 0c 01 c0 a8 0c 02", "tr:last-child td:last-child", "192.168.12.2");
|
testOpHtml(browser, "Parse IPv4 header", "45 c0 00 c4 02 89 00 00 ff 11 1e 8c c0 a8 0c 01 c0 a8 0c 02", "tr:nth-last-child(2) td:last-child", "192.168.12.2");
|
||||||
// testOp(browser, "Parse IPv6 address", "test input", "test_output");
|
// testOp(browser, "Parse IPv6 address", "test input", "test_output");
|
||||||
// testOp(browser, "Parse ObjectID timestamp", "test input", "test_output");
|
// testOp(browser, "Parse ObjectID timestamp", "test input", "test_output");
|
||||||
// testOp(browser, "Parse QR Code", "test input", "test_output");
|
// testOp(browser, "Parse QR Code", "test input", "test_output");
|
||||||
@ -335,6 +354,7 @@ module.exports = {
|
|||||||
// testOp(browser, "Tail", "test input", "test_output");
|
// testOp(browser, "Tail", "test input", "test_output");
|
||||||
// testOp(browser, "Take bytes", "test input", "test_output");
|
// testOp(browser, "Take bytes", "test input", "test_output");
|
||||||
testOp(browser, "Tar", "test input", /^file\.txt\x00{92}/);
|
testOp(browser, "Tar", "test input", /^file\.txt\x00{92}/);
|
||||||
|
testOp(browser, "Template", "{\"one\": 1, \"two\": 2}", "1 2", ["{{ one }} {{ two }}"]);
|
||||||
testOpHtml(browser, "Text Encoding Brute Force", "test input", "tr:nth-of-type(4) td:last-child", /t\u2400e\u2400s\u2400t\u2400/);
|
testOpHtml(browser, "Text Encoding Brute Force", "test input", "tr:nth-of-type(4) td:last-child", /t\u2400e\u2400s\u2400t\u2400/);
|
||||||
// testOp(browser, "To BCD", "test input", "test_output");
|
// testOp(browser, "To BCD", "test input", "test_output");
|
||||||
// testOp(browser, "To Base", "test input", "test_output");
|
// testOp(browser, "To Base", "test input", "test_output");
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import "./tests/Dish.mjs";
|
|||||||
import "./tests/NodeDish.mjs";
|
import "./tests/NodeDish.mjs";
|
||||||
import "./tests/Utils.mjs";
|
import "./tests/Utils.mjs";
|
||||||
import "./tests/Categories.mjs";
|
import "./tests/Categories.mjs";
|
||||||
|
import "./tests/lib/BigIntUtils.mjs";
|
||||||
|
|
||||||
const testStatus = {
|
const testStatus = {
|
||||||
allTestsPassing: true,
|
allTestsPassing: true,
|
||||||
|
|||||||
150
tests/node/tests/lib/BigIntUtils.mjs
Normal file
150
tests/node/tests/lib/BigIntUtils.mjs
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import TestRegister from "../../../lib/TestRegister.mjs";
|
||||||
|
import { parseBigInt, egcd, modPow } from "../../../../src/core/lib/BigIntUtils.mjs";
|
||||||
|
import it from "../../assertionHandler.mjs";
|
||||||
|
import assert from "assert";
|
||||||
|
|
||||||
|
TestRegister.addApiTests([
|
||||||
|
// ===== parseBigInt tests =====
|
||||||
|
it("BigIntUtils: parseBigInt - decimal number", () => {
|
||||||
|
const value = parseBigInt("1", "test value");
|
||||||
|
assert.deepStrictEqual(value, BigInt("1"));
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("BigIntUtils: parseBigInt - large decimal", () => {
|
||||||
|
const value = parseBigInt("123456789012345678901234567890", "test value");
|
||||||
|
assert.deepStrictEqual(value, BigInt("123456789012345678901234567890"));
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("BigIntUtils: parseBigInt - hexadecimal lowercase", () => {
|
||||||
|
const value = parseBigInt("0xff", "test value");
|
||||||
|
assert.deepStrictEqual(value, BigInt("255"));
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("BigIntUtils: parseBigInt - hexadecimal uppercase", () => {
|
||||||
|
const value = parseBigInt("0xFF", "test value");
|
||||||
|
assert.deepStrictEqual(value, BigInt("255"));
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("BigIntUtils: parseBigInt - large hexadecimal", () => {
|
||||||
|
const value = parseBigInt("0x123456789ABCDEF", "test value");
|
||||||
|
assert.deepStrictEqual(value, BigInt("0x123456789ABCDEF"));
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("BigIntUtils: parseBigInt - whitespace trimming", () => {
|
||||||
|
const value = parseBigInt(" 42 ", "test value");
|
||||||
|
assert.deepStrictEqual(value, BigInt("42"));
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("BigIntUtils: parseBigInt - invalid input (text)", () => {
|
||||||
|
assert.throws(() => parseBigInt("test", "test value"), {
|
||||||
|
name: "Error",
|
||||||
|
message: "test value must be decimal or hex (0x...)"
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("BigIntUtils: parseBigInt - invalid input (hex without prefix)", () => {
|
||||||
|
assert.throws(() => parseBigInt("FF", "test value"), {
|
||||||
|
name: "Error",
|
||||||
|
message: "test value must be decimal or hex (0x...)"
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("BigIntUtils: parseBigInt - invalid input (mixed)", () => {
|
||||||
|
assert.throws(() => parseBigInt("12abc", "test value"), {
|
||||||
|
name: "Error",
|
||||||
|
message: "test value must be decimal or hex (0x...)"
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
// ===== egcd tests =====
|
||||||
|
it("BigIntUtils: egcd - basic coprime", () => {
|
||||||
|
const a = BigInt("36");
|
||||||
|
const b = BigInt("48");
|
||||||
|
const gcd = BigInt("12");
|
||||||
|
const bezout1 = BigInt("-1");
|
||||||
|
const bezout2 = BigInt("1");
|
||||||
|
assert.deepStrictEqual(egcd(a, b), [gcd, bezout1, bezout2]);
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("BigIntUtils: egcd - coprime numbers", () => {
|
||||||
|
const [g, x, y] = egcd(BigInt("3"), BigInt("11"));
|
||||||
|
assert.strictEqual(g, BigInt("1"));
|
||||||
|
// Verify Bézout identity: a*x + b*y = gcd
|
||||||
|
assert.strictEqual(BigInt("3") * x + BigInt("11") * y, g);
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("BigIntUtils: egcd - non-coprime numbers", () => {
|
||||||
|
const [g, x, y] = egcd(BigInt("240"), BigInt("46"));
|
||||||
|
assert.strictEqual(g, BigInt("2"));
|
||||||
|
// Verify Bézout identity
|
||||||
|
assert.strictEqual(BigInt("240") * x + BigInt("46") * y, g);
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("BigIntUtils: egcd - with zero", () => {
|
||||||
|
const [g, x, y] = egcd(BigInt("17"), BigInt("0"));
|
||||||
|
assert.strictEqual(g, BigInt("17"));
|
||||||
|
assert.strictEqual(x, BigInt("1"));
|
||||||
|
assert.strictEqual(y, BigInt("0"));
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("BigIntUtils: egcd - identical numbers", () => {
|
||||||
|
const [g, x, y] = egcd(BigInt("42"), BigInt("42"));
|
||||||
|
assert.strictEqual(g, BigInt("42"));
|
||||||
|
// Verify Bézout identity
|
||||||
|
assert.strictEqual(BigInt("42") * x + BigInt("42") * y, g);
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("BigIntUtils: egcd - large numbers", () => {
|
||||||
|
const a = BigInt("123456789012345678901234567890");
|
||||||
|
const b = BigInt("987654321098765432109876543210");
|
||||||
|
const [g, x, y] = egcd(a, b);
|
||||||
|
// Verify Bézout identity
|
||||||
|
assert.strictEqual(a * x + b * y, g);
|
||||||
|
}),
|
||||||
|
|
||||||
|
// ===== modPow tests =====
|
||||||
|
it("BigIntUtils: modPow - basic", () => {
|
||||||
|
// 2^10 mod 1000 = 1024 mod 1000 = 24
|
||||||
|
const result = modPow(BigInt("2"), BigInt("10"), BigInt("1000"));
|
||||||
|
assert.strictEqual(result, BigInt("24"));
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("BigIntUtils: modPow - RSA-like example", () => {
|
||||||
|
// Common RSA public exponent
|
||||||
|
const base = BigInt("123456789");
|
||||||
|
const exp = BigInt("65537");
|
||||||
|
const mod = BigInt("999999999999");
|
||||||
|
const result = modPow(base, exp, mod);
|
||||||
|
// Result should be less than modulus
|
||||||
|
assert(result < mod);
|
||||||
|
assert(result >= BigInt("0"));
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("BigIntUtils: modPow - exponent zero", () => {
|
||||||
|
// Any number^0 = 1
|
||||||
|
const result = modPow(BigInt("999"), BigInt("0"), BigInt("100"));
|
||||||
|
assert.strictEqual(result, BigInt("1"));
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("BigIntUtils: modPow - base zero", () => {
|
||||||
|
// 0^n = 0
|
||||||
|
const result = modPow(BigInt("0"), BigInt("5"), BigInt("100"));
|
||||||
|
assert.strictEqual(result, BigInt("0"));
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("BigIntUtils: modPow - large exponent", () => {
|
||||||
|
// Test with very large exponent (efficient algorithm should handle this)
|
||||||
|
const result = modPow(BigInt("3"), BigInt("1000000"), BigInt("1000000007"));
|
||||||
|
assert(result >= BigInt("0"));
|
||||||
|
assert(result < BigInt("1000000007"));
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("BigIntUtils: modPow - modular inverse verification", () => {
|
||||||
|
// If a*x . 1 (mod m), then modPow(a, 1, m) * x . 1 (mod m)
|
||||||
|
const a = BigInt("3");
|
||||||
|
const m = BigInt("11");
|
||||||
|
const x = BigInt("4"); // inverse of 3 mod 11
|
||||||
|
const result = modPow(a, BigInt("1"), m) * x % m;
|
||||||
|
assert.strictEqual(result, BigInt("1"));
|
||||||
|
}),
|
||||||
|
]);
|
||||||
@ -589,8 +589,7 @@ Password: 282760`;
|
|||||||
...[1, 3, 4, 5, 6, 7].map(version => it(`Analyze UUID v${version}`, () => {
|
...[1, 3, 4, 5, 6, 7].map(version => it(`Analyze UUID v${version}`, () => {
|
||||||
const uuid = chef.generateUUID("", { "version": `v${version}` }).toString();
|
const uuid = chef.generateUUID("", { "version": `v${version}` }).toString();
|
||||||
const result = chef.analyseUUID(uuid).toString();
|
const result = chef.analyseUUID(uuid).toString();
|
||||||
const expected = `UUID version: ${version}`;
|
assert.ok(result.startsWith(`Version:\n${version}\n`), `Expected output to start with "Version:\\n${version}\\n", got: ${result}`);
|
||||||
assert.strictEqual(result, expected);
|
|
||||||
})),
|
})),
|
||||||
|
|
||||||
it("Generate UUID using defaults", () => {
|
it("Generate UUID using defaults", () => {
|
||||||
@ -598,7 +597,7 @@ Password: 282760`;
|
|||||||
assert.ok(uuid);
|
assert.ok(uuid);
|
||||||
|
|
||||||
const analysis = chef.analyseUUID(uuid).toString();
|
const analysis = chef.analyseUUID(uuid).toString();
|
||||||
assert.strictEqual(analysis, "UUID version: 4");
|
assert.ok(analysis.startsWith("Version:\n4\n"), `Expected output to start with "Version:\\n4\\n", got: ${analysis}`);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
it("Gzip, Gunzip", () => {
|
it("Gzip, Gunzip", () => {
|
||||||
@ -867,13 +866,15 @@ pCGTErs=
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
it("SQL Beautify", () => {
|
it("SQL Beautify", () => {
|
||||||
const result = chef.SQLBeautify(`SELECT MONTH, ID, RAIN_I, TEMP_F
|
const result = chef.SQLBeautify(`SELECT MONTH, ID, RAIN_I, TEMP_F FROM STATS;`);
|
||||||
FROM STATS;`);
|
const expected =
|
||||||
const expected = `SELECT MONTH,
|
`SELECT
|
||||||
ID,
|
MONTH,
|
||||||
RAIN_I,
|
ID,
|
||||||
TEMP_F
|
RAIN_I,
|
||||||
FROM STATS;`;
|
TEMP_F
|
||||||
|
FROM
|
||||||
|
STATS;`;
|
||||||
assert.strictEqual(result.toString(), expected);
|
assert.strictEqual(result.toString(), expected);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,9 @@
|
|||||||
import { setLongTestFailure, logTestReport } from "../lib/utils.mjs";
|
import { setLongTestFailure, logTestReport } from "../lib/utils.mjs";
|
||||||
|
|
||||||
import TestRegister from "../lib/TestRegister.mjs";
|
import TestRegister from "../lib/TestRegister.mjs";
|
||||||
|
import "./tests/A1Z26CipherDecode.mjs";
|
||||||
import "./tests/AESKeyWrap.mjs";
|
import "./tests/AESKeyWrap.mjs";
|
||||||
|
import "./tests/AnalyseUUID.mjs";
|
||||||
import "./tests/AlternatingCaps.mjs";
|
import "./tests/AlternatingCaps.mjs";
|
||||||
import "./tests/AvroToJSON.mjs";
|
import "./tests/AvroToJSON.mjs";
|
||||||
import "./tests/BaconCipher.mjs";
|
import "./tests/BaconCipher.mjs";
|
||||||
@ -61,10 +63,12 @@ import "./tests/Crypt.mjs";
|
|||||||
import "./tests/CSV.mjs";
|
import "./tests/CSV.mjs";
|
||||||
import "./tests/DateTime.mjs";
|
import "./tests/DateTime.mjs";
|
||||||
import "./tests/DefangIP.mjs";
|
import "./tests/DefangIP.mjs";
|
||||||
|
import "./tests/DisassembleARM.mjs";
|
||||||
import "./tests/DropNthBytes.mjs";
|
import "./tests/DropNthBytes.mjs";
|
||||||
import "./tests/ECDSA.mjs";
|
import "./tests/ECDSA.mjs";
|
||||||
import "./tests/ELFInfo.mjs";
|
import "./tests/ELFInfo.mjs";
|
||||||
import "./tests/Enigma.mjs";
|
import "./tests/Enigma.mjs";
|
||||||
|
import "./tests/ExtractAudioMetadata.mjs";
|
||||||
import "./tests/ExtractEmailAddresses.mjs";
|
import "./tests/ExtractEmailAddresses.mjs";
|
||||||
import "./tests/ExtractHashes.mjs";
|
import "./tests/ExtractHashes.mjs";
|
||||||
import "./tests/ExtractIPAddresses.mjs";
|
import "./tests/ExtractIPAddresses.mjs";
|
||||||
@ -76,6 +80,7 @@ import "./tests/FromDecimal.mjs";
|
|||||||
import "./tests/GenerateAllChecksums.mjs";
|
import "./tests/GenerateAllChecksums.mjs";
|
||||||
import "./tests/GenerateAllHashes.mjs";
|
import "./tests/GenerateAllHashes.mjs";
|
||||||
import "./tests/GenerateDeBruijnSequence.mjs";
|
import "./tests/GenerateDeBruijnSequence.mjs";
|
||||||
|
import "./tests/GenerateQRCode.mjs";
|
||||||
import "./tests/GetAllCasings.mjs";
|
import "./tests/GetAllCasings.mjs";
|
||||||
import "./tests/GOST.mjs";
|
import "./tests/GOST.mjs";
|
||||||
import "./tests/Gunzip.mjs";
|
import "./tests/Gunzip.mjs";
|
||||||
@ -118,6 +123,7 @@ import "./tests/NetBIOS.mjs";
|
|||||||
import "./tests/NormaliseUnicode.mjs";
|
import "./tests/NormaliseUnicode.mjs";
|
||||||
import "./tests/NTLM.mjs";
|
import "./tests/NTLM.mjs";
|
||||||
import "./tests/OTP.mjs";
|
import "./tests/OTP.mjs";
|
||||||
|
import "./tests/ParseEthernetFrame.mjs";
|
||||||
import "./tests/ParseIPRange.mjs";
|
import "./tests/ParseIPRange.mjs";
|
||||||
import "./tests/ParseObjectIDTimestamp.mjs";
|
import "./tests/ParseObjectIDTimestamp.mjs";
|
||||||
import "./tests/ParseQRCode.mjs";
|
import "./tests/ParseQRCode.mjs";
|
||||||
@ -138,6 +144,7 @@ import "./tests/Rabbit.mjs";
|
|||||||
import "./tests/RAKE.mjs";
|
import "./tests/RAKE.mjs";
|
||||||
import "./tests/Regex.mjs";
|
import "./tests/Regex.mjs";
|
||||||
import "./tests/Register.mjs";
|
import "./tests/Register.mjs";
|
||||||
|
import "./tests/RegularExpression.mjs";
|
||||||
import "./tests/RisonEncodeDecode.mjs";
|
import "./tests/RisonEncodeDecode.mjs";
|
||||||
import "./tests/Rotate.mjs";
|
import "./tests/Rotate.mjs";
|
||||||
import "./tests/RSA.mjs";
|
import "./tests/RSA.mjs";
|
||||||
@ -151,7 +158,9 @@ import "./tests/Shuffle.mjs";
|
|||||||
import "./tests/SIGABA.mjs";
|
import "./tests/SIGABA.mjs";
|
||||||
import "./tests/SM2.mjs";
|
import "./tests/SM2.mjs";
|
||||||
import "./tests/SM4.mjs";
|
import "./tests/SM4.mjs";
|
||||||
|
import "./tests/RC6.mjs";
|
||||||
// import "./tests/SplitColourChannels.mjs"; // Cannot test operations that use the File type yet
|
// import "./tests/SplitColourChannels.mjs"; // Cannot test operations that use the File type yet
|
||||||
|
import "./tests/SQLBeautify.mjs";
|
||||||
import "./tests/StrUtils.mjs";
|
import "./tests/StrUtils.mjs";
|
||||||
import "./tests/StripIPv4Header.mjs";
|
import "./tests/StripIPv4Header.mjs";
|
||||||
import "./tests/StripTCPHeader.mjs";
|
import "./tests/StripTCPHeader.mjs";
|
||||||
@ -162,6 +171,7 @@ import "./tests/SymmetricDifference.mjs";
|
|||||||
import "./tests/TakeNthBytes.mjs";
|
import "./tests/TakeNthBytes.mjs";
|
||||||
import "./tests/Template.mjs";
|
import "./tests/Template.mjs";
|
||||||
import "./tests/TextEncodingBruteForce.mjs";
|
import "./tests/TextEncodingBruteForce.mjs";
|
||||||
|
import "./tests/TextIntegerConverter.mjs";
|
||||||
import "./tests/ToFromInsensitiveRegex.mjs";
|
import "./tests/ToFromInsensitiveRegex.mjs";
|
||||||
import "./tests/TranslateDateTimeFormat.mjs";
|
import "./tests/TranslateDateTimeFormat.mjs";
|
||||||
import "./tests/Typex.mjs";
|
import "./tests/Typex.mjs";
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user